Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally use route as transaction name in Django 2.2+ #396

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/configuration.asciidoc
Expand Up @@ -586,6 +586,25 @@ Verification can be disabled by changing this setting to `False`.
NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python 3.4.3+.


[float]
[[config-django-specific]]
=== Django-specific configuration

[float]
[[config-django-transaction-name-from-route]]
==== `django_transaction_name_from_route`
|============
| Environment | Django | Default
| `ELASTIC_APM_DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `False`
|============


By default, we use the function or class name of the view as the transaction name.
Starting with Django 2.2, Django makes the route (e.g. `users/<int:user_id>/`) available on the `request.resolver_match` object.
If you want to use the route instead of the view name as the transaction name, you can set this config option to `true`.

NOTE: in versions previous to Django 2.2, changing this setting will have no effect.

[float]
[[config-formats]]
=== Configuration formats
Expand Down
15 changes: 15 additions & 0 deletions docs/django.asciidoc
Expand Up @@ -114,6 +114,21 @@ ELASTIC_APM['TRANSACTIONS_IGNORE_PATTERNS'] = ['^OPTIONS ', 'views.api.v2']

This example ignores any requests using the `OPTIONS` method and any requests containing `views.api.v2`.

[float]
[[django-transaction-name-route]]
==== Using the route as transaction name

By default, we use the function or class name of the view as the transaction name.
Starting with Django 2.2, Django makes the route (e.g. `users/<int:user_id>/`) available on the `request.resolver_match` object.
If you want to use the route instead of the view name as the transaction name, you can set the <<config-django-transaction-name-from-route,`django_transaction_name_from_route`>> config option to `true`.

[source,python]
----
ELASTIC_APM['DJANGO_TRANSACTION_NAME_FROM_ROUTE'] = True
----

NOTE: in versions previous to Django 2.2, changing this setting will have no effect.

[float]
[[django-integrating-with-the-rum-agent]]
==== Integrating with the RUM agent
Expand Down
1 change: 1 addition & 0 deletions elasticapm/conf/__init__.py
Expand Up @@ -252,6 +252,7 @@ class Config(_ConfigBase):
instrument = _BoolConfigValue("DISABLE_INSTRUMENTATION", default=True)
enable_distributed_tracing = _BoolConfigValue("ENABLE_DISTRIBUTED_TRACING", default=True)
capture_headers = _BoolConfigValue("CAPTURE_HEADERS", default=True)
django_transaction_name_from_route = _BoolConfigValue("DJANGO_TRANSACTION_NAME_FROM_ROUTE", default=False)


def setup_logging(handler, exclude=("gunicorn", "south", "elasticapm.errors")):
Expand Down
6 changes: 5 additions & 1 deletion elasticapm/contrib/django/middleware/__init__.py
Expand Up @@ -146,8 +146,12 @@ def process_response(self, request, response):
return response
try:
if hasattr(response, "status_code"):
if getattr(request, "_elasticapm_view_func", False):
transaction_name = None
if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"):
transaction_name = request.resolver_match.route
elif getattr(request, "_elasticapm_view_func", False):
transaction_name = get_name_from_func(request._elasticapm_view_func)
if transaction_name:
transaction_name = build_name_with_http_method_prefix(transaction_name, request)
elasticapm.set_transaction_name(transaction_name, override=False)

Expand Down
6 changes: 6 additions & 0 deletions tests/.jenkins_exclude.yml
Expand Up @@ -4,10 +4,14 @@ exclude:
FRAMEWORK: django-2.0
- PYTHON_VERSION: python-2.7
FRAMEWORK: django-2.1
- PYTHON_VERSION: python-2.7
FRAMEWORK: django-2.2
- PYTHON_VERSION: python-2.7
FRAMEWORK: django-master
- PYTHON_VERSION: python-3.4
FRAMEWORK: django-2.1
- PYTHON_VERSION: python-3.4
FRAMEWORK: django-2.2
- PYTHON_VERSION: python-3.4
FRAMEWORK: django-master
- PYTHON_VERSION: python-3.7
Expand All @@ -16,6 +20,8 @@ exclude:
FRAMEWORK: django-2.0
- PYTHON_VERSION: pypy-2
FRAMEWORK: django-2.1
- PYTHON_VERSION: pypy-2
FRAMEWORK: django-2.2
- PYTHON_VERSION: pypy-2
FRAMEWORK: django-master
# Flask
Expand Down
1 change: 1 addition & 0 deletions tests/.jenkins_framework.yml
Expand Up @@ -4,6 +4,7 @@ FRAMEWORK:
- django-1.11
- django-2.0
- django-2.1
- django-2.2
- flask-0.12
- flask-1.0
- opentracing-newest
Expand Down
1 change: 1 addition & 0 deletions tests/.jenkins_framework_full.yml
Expand Up @@ -7,6 +7,7 @@ FRAMEWORK:
- django-1.11
- django-2.0
- django-2.1
- django-2.2
# - django-master
- flask-0.10
- flask-0.11
Expand Down
22 changes: 22 additions & 0 deletions tests/contrib/django/django_tests.py
Expand Up @@ -1412,3 +1412,25 @@ def test_rum_tracing_context_processor(client, django_elasticapm_client):
assert response.context["apm"]["is_sampled"]
assert response.context["apm"]["is_sampled_js"] == "true"
assert callable(response.context["apm"]["span_id"])


@pytest.mark.skipif(django.VERSION < (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
def test_transaction_name_from_route(client, django_elasticapm_client):
with override_settings(
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
):
client.get("/route/1/")
transaction = django_elasticapm_client.events[TRANSACTION][0]
assert transaction["name"] == "GET route/<int:id>/"


@pytest.mark.skipif(django.VERSION >= (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
def test_transaction_name_from_route_doesnt_have_effect_in_older_django(client, django_elasticapm_client):
with override_settings(
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
):
client.get("/no-error")
transaction = django_elasticapm_client.events[TRANSACTION][0]
assert transaction["name"] == "GET tests.contrib.django.testapp.views.no_error"
5 changes: 5 additions & 0 deletions tests/contrib/django/testapp/urls.py
Expand Up @@ -35,3 +35,8 @@ def handler500(request):

if django.VERSION >= (1, 8):
urlpatterns += (url(r"^render-jinja2-template$", views.render_jinja2_template, name="render-jinja2-template"),)

if django.VERSION >= (2, 2):
from django.urls import path

urlpatterns += (path("route/<int:id>/", views.no_error, name="route-view"),)
5 changes: 3 additions & 2 deletions tests/contrib/django/testapp/views.py
Expand Up @@ -8,6 +8,7 @@
from django.shortcuts import get_object_or_404, render

import elasticapm
from elasticapm.utils import compat


class MyException(Exception):
Expand All @@ -18,8 +19,8 @@ class IgnoredException(Exception):
skip_elasticapm = True


def no_error(request):
resp = HttpResponse("")
def no_error(request, id=None):
resp = HttpResponse(compat.text_type(id))
resp["My-Header"] = "foo"
return resp

Expand Down
2 changes: 2 additions & 0 deletions tests/requirements/requirements-django-2.2.txt
@@ -0,0 +1,2 @@
Django>=2.2a1,<2.3
-r requirements-base.txt