Skip to content

Commit 4025b99

Browse files
authored
fall back on url_name and func name if no route name is provided with Django (#1095)
1 parent b52e63e commit 4025b99

File tree

4 files changed

+70
-2
lines changed

4 files changed

+70
-2
lines changed

elasticapm/contrib/django/middleware/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ def process_response(self, request, response):
169169
if hasattr(response, "status_code"):
170170
transaction_name = None
171171
if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"):
172-
transaction_name = request.resolver_match.route
172+
r = request.resolver_match
173+
# if no route is defined (e.g. for the root URL), fall back on url_name and then function name
174+
transaction_name = r.route or r.url_name or get_name_from_func(r.func)
173175
elif getattr(request, "_elasticapm_view_func", False):
174176
transaction_name = get_name_from_func(request._elasticapm_view_func)
175177
if transaction_name:

tests/contrib/django/django_tests.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,29 @@ def test_transaction_name_from_route(client, django_elasticapm_client):
15311531
assert transaction["name"] == "GET route/<int:id>/"
15321532

15331533

1534+
@pytest.mark.skipif(django.VERSION < (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
1535+
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
1536+
def test_transaction_name_from_route_empty_route(client, django_elasticapm_client):
1537+
with override_settings(
1538+
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
1539+
):
1540+
client.get("/")
1541+
transaction = django_elasticapm_client.events[TRANSACTION][0]
1542+
assert transaction["name"] == "GET home-view"
1543+
1544+
1545+
@pytest.mark.skipif(django.VERSION < (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
1546+
@pytest.mark.urls("tests.contrib.django.testapp.nameless_route_urls")
1547+
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
1548+
def test_transaction_name_from_route_empty_route_no_name(client, django_elasticapm_client):
1549+
with override_settings(
1550+
**middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.TracingMiddleware"])
1551+
):
1552+
client.get("/")
1553+
transaction = django_elasticapm_client.events[TRANSACTION][0]
1554+
assert transaction["name"] == "GET tests.contrib.django.testapp.views.no_error"
1555+
1556+
15341557
@pytest.mark.skipif(django.VERSION >= (2, 2), reason="ResolverMatch.route attribute is new in Django 2.2")
15351558
@pytest.mark.parametrize("django_elasticapm_client", [{"django_transaction_name_from_route": "true"}], indirect=True)
15361559
def test_transaction_name_from_route_doesnt_have_effect_in_older_django(client, django_elasticapm_client):
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# BSD 3-Clause License
2+
#
3+
# Copyright (c) 2021, Elasticsearch BV
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
#
9+
# * Redistributions of source code must retain the above copyright notice, this
10+
# list of conditions and the following disclaimer.
11+
#
12+
# * Redistributions in binary form must reproduce the above copyright notice,
13+
# this list of conditions and the following disclaimer in the documentation
14+
# and/or other materials provided with the distribution.
15+
#
16+
# * Neither the name of the copyright holder nor the names of its
17+
# contributors may be used to endorse or promote products derived from
18+
# this software without specific prior written permission.
19+
#
20+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
from __future__ import absolute_import
32+
33+
import django
34+
35+
from tests.contrib.django.testapp import views
36+
37+
if django.VERSION >= (2, 2):
38+
from django.urls import path
39+
40+
urlpatterns = (path("", views.no_error),)

tests/contrib/django/testapp/urls.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,7 @@ def handler500(request):
7575
if django.VERSION >= (2, 2):
7676
from django.urls import path
7777

78-
urlpatterns += (path("route/<int:id>/", views.no_error, name="route-view"),)
78+
urlpatterns += (
79+
path("route/<int:id>/", views.no_error, name="route-view"),
80+
path("", views.no_error, name="home-view"),
81+
)

0 commit comments

Comments
 (0)