Skip to content

Commit d4e8388

Browse files
committed
optionally use route as transaction name in Django 2.2+ (#396)
fixes #86 closes #396
1 parent 1d820ea commit d4e8388

File tree

12 files changed

+81
-3
lines changed

12 files changed

+81
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[Check the diff](https://github.com/elastic/apm-agent-python/compare/v4.1.0...master)
55

66
* Moved context.url to context.http.url for requests/urllib3 spans (#393, #394)
7+
* Added support for using route as transaction name in Django 2.2+ (#86, #396)
78

89
## v4.1.0
910
[Check the diff](https://github.com/elastic/apm-agent-python/compare/v4.0.3...v4.1.0)

docs/configuration.asciidoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,25 @@ Verification can be disabled by changing this setting to `False`.
586586
NOTE: SSL certificate verification is only available in Python 2.7.9+ and Python 3.4.3+.
587587

588588

589+
[float]
590+
[[config-django-specific]]
591+
=== Django-specific configuration
592+
593+
[float]
594+
[[config-django-transaction-name-from-route]]
595+
==== `django_transaction_name_from_route`
596+
|============
597+
| Environment | Django | Default
598+
| `ELASTIC_APM_DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `False`
599+
|============
600+
601+
602+
By default, we use the function or class name of the view as the transaction name.
603+
Starting with Django 2.2, Django makes the route (e.g. `users/<int:user_id>/`) available on the `request.resolver_match` object.
604+
If you want to use the route instead of the view name as the transaction name, you can set this config option to `true`.
605+
606+
NOTE: in versions previous to Django 2.2, changing this setting will have no effect.
607+
589608
[float]
590609
[[config-formats]]
591610
=== Configuration formats

docs/django.asciidoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ ELASTIC_APM['TRANSACTIONS_IGNORE_PATTERNS'] = ['^OPTIONS ', 'views.api.v2']
114114

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

117+
[float]
118+
[[django-transaction-name-route]]
119+
==== Using the route as transaction name
120+
121+
By default, we use the function or class name of the view as the transaction name.
122+
Starting with Django 2.2, Django makes the route (e.g. `users/<int:user_id>/`) available on the `request.resolver_match` object.
123+
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`.
124+
125+
[source,python]
126+
----
127+
ELASTIC_APM['DJANGO_TRANSACTION_NAME_FROM_ROUTE'] = True
128+
----
129+
130+
NOTE: in versions previous to Django 2.2, changing this setting will have no effect.
131+
117132
[float]
118133
[[django-integrating-with-the-rum-agent]]
119134
==== Integrating with the RUM agent

elasticapm/conf/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ class Config(_ConfigBase):
252252
instrument = _BoolConfigValue("DISABLE_INSTRUMENTATION", default=True)
253253
enable_distributed_tracing = _BoolConfigValue("ENABLE_DISTRIBUTED_TRACING", default=True)
254254
capture_headers = _BoolConfigValue("CAPTURE_HEADERS", default=True)
255+
django_transaction_name_from_route = _BoolConfigValue("DJANGO_TRANSACTION_NAME_FROM_ROUTE", default=False)
255256

256257

257258
def setup_logging(handler, exclude=("gunicorn", "south", "elasticapm.errors")):

elasticapm/contrib/django/middleware/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,12 @@ def process_response(self, request, response):
146146
return response
147147
try:
148148
if hasattr(response, "status_code"):
149-
if getattr(request, "_elasticapm_view_func", False):
149+
transaction_name = None
150+
if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"):
151+
transaction_name = request.resolver_match.route
152+
elif getattr(request, "_elasticapm_view_func", False):
150153
transaction_name = get_name_from_func(request._elasticapm_view_func)
154+
if transaction_name:
151155
transaction_name = build_name_with_http_method_prefix(transaction_name, request)
152156
elasticapm.set_transaction_name(transaction_name, override=False)
153157

tests/.jenkins_exclude.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ exclude:
44
FRAMEWORK: django-2.0
55
- PYTHON_VERSION: python-2.7
66
FRAMEWORK: django-2.1
7+
- PYTHON_VERSION: python-2.7
8+
FRAMEWORK: django-2.2
79
- PYTHON_VERSION: python-2.7
810
FRAMEWORK: django-master
911
- PYTHON_VERSION: python-3.4
1012
FRAMEWORK: django-2.1
13+
- PYTHON_VERSION: python-3.4
14+
FRAMEWORK: django-2.2
1115
- PYTHON_VERSION: python-3.4
1216
FRAMEWORK: django-master
1317
- PYTHON_VERSION: python-3.7
@@ -16,6 +20,8 @@ exclude:
1620
FRAMEWORK: django-2.0
1721
- PYTHON_VERSION: pypy-2
1822
FRAMEWORK: django-2.1
23+
- PYTHON_VERSION: pypy-2
24+
FRAMEWORK: django-2.2
1925
- PYTHON_VERSION: pypy-2
2026
FRAMEWORK: django-master
2127
# Flask

tests/.jenkins_framework.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ FRAMEWORK:
44
- django-1.11
55
- django-2.0
66
- django-2.1
7+
- django-2.2
78
- flask-0.12
89
- flask-1.0
910
- opentracing-newest

tests/.jenkins_framework_full.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ FRAMEWORK:
77
- django-1.11
88
- django-2.0
99
- django-2.1
10+
- django-2.2
1011
# - django-master
1112
- flask-0.10
1213
- flask-0.11

tests/contrib/django/django_tests.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,3 +1412,25 @@ def test_rum_tracing_context_processor(client, django_elasticapm_client):
14121412
assert response.context["apm"]["is_sampled"]
14131413
assert response.context["apm"]["is_sampled_js"] == "true"
14141414
assert callable(response.context["apm"]["span_id"])
1415+
1416+
1417+
@pytest.mark.skipif(django.VERSION < (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
1418+
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
1419+
def test_transaction_name_from_route(client, django_elasticapm_client):
1420+
with override_settings(
1421+
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
1422+
):
1423+
client.get("/route/1/")
1424+
transaction = django_elasticapm_client.events[TRANSACTION][0]
1425+
assert transaction["name"] == "GET route/<int:id>/"
1426+
1427+
1428+
@pytest.mark.skipif(django.VERSION >= (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
1429+
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
1430+
def test_transaction_name_from_route_doesnt_have_effect_in_older_django(client, django_elasticapm_client):
1431+
with override_settings(
1432+
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
1433+
):
1434+
client.get("/no-error")
1435+
transaction = django_elasticapm_client.events[TRANSACTION][0]
1436+
assert transaction["name"] == "GET tests.contrib.django.testapp.views.no_error"

tests/contrib/django/testapp/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@ def handler500(request):
3535

3636
if django.VERSION >= (1, 8):
3737
urlpatterns += (url(r"^render-jinja2-template$", views.render_jinja2_template, name="render-jinja2-template"),)
38+
39+
if django.VERSION >= (2, 2):
40+
from django.urls import path
41+
42+
urlpatterns += (path("route/<int:id>/", views.no_error, name="route-view"),)

tests/contrib/django/testapp/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.shortcuts import get_object_or_404, render
99

1010
import elasticapm
11+
from elasticapm.utils import compat
1112

1213

1314
class MyException(Exception):
@@ -18,8 +19,8 @@ class IgnoredException(Exception):
1819
skip_elasticapm = True
1920

2021

21-
def no_error(request):
22-
resp = HttpResponse("")
22+
def no_error(request, id=None):
23+
resp = HttpResponse(compat.text_type(id))
2324
resp["My-Header"] = "foo"
2425
return resp
2526

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Django>=2.2a1,<2.3
2+
-r requirements-base.txt

0 commit comments

Comments
 (0)