Skip to content

Commit 199529b

Browse files
HiveTraumMichael Stella
authored and
Michael Stella
committed
Django span names according to convention (open-telemetry#992)
1 parent 3cafc45 commit 199529b

File tree

4 files changed

+82
-12
lines changed

4 files changed

+82
-12
lines changed

instrumentation/opentelemetry-instrumentation-django/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Changed span name extraction from request to comply semantic convention ([#992](https://github.com/open-telemetry/opentelemetry-python/pull/992))
6+
57
## Version 0.13b0
68

79
Released 2020-09-17

instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py

+32-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
from opentelemetry.trace import SpanKind, get_tracer
2727
from opentelemetry.util import ExcludeList
2828

29+
try:
30+
from django.core.urlresolvers import ( # pylint: disable=no-name-in-module
31+
resolve,
32+
Resolver404,
33+
)
34+
except ImportError:
35+
from django.urls import resolve, Resolver404
36+
2937
try:
3038
from django.utils.deprecation import MiddlewareMixin
3139
except ImportError:
@@ -50,17 +58,33 @@ class _DjangoMiddleware(MiddlewareMixin):
5058
else:
5159
_excluded_urls = ExcludeList(_excluded_urls)
5260

53-
def process_view(
54-
self, request, view_func, view_args, view_kwargs
55-
): # pylint: disable=unused-argument
61+
@staticmethod
62+
def _get_span_name(request):
63+
try:
64+
if getattr(request, "resolver_match"):
65+
match = request.resolver_match
66+
else:
67+
match = resolve(request.get_full_path())
68+
69+
if hasattr(match, "route"):
70+
return match.route
71+
72+
# Instead of using `view_name`, better to use `_func_name` as some applications can use similar
73+
# view names in different modules
74+
if hasattr(match, "_func_name"):
75+
return match._func_name # pylint: disable=protected-access
76+
77+
# Fallback for safety as `_func_name` private field
78+
return match.view_name
79+
80+
except Resolver404:
81+
return "HTTP {}".format(request.method)
82+
83+
def process_request(self, request):
5684
# request.META is a dictionary containing all available HTTP headers
5785
# Read more about request.META here:
5886
# https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META
5987

60-
# environ = {
61-
# key.lower().replace('_', '-').replace("http-", "", 1): value
62-
# for key, value in request.META.items()
63-
# }
6488
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
6589
return
6690

@@ -73,7 +97,7 @@ def process_view(
7397
attributes = collect_request_attributes(environ)
7498

7599
span = tracer.start_span(
76-
view_func.__name__,
100+
self._get_span_name(request),
77101
kind=SpanKind.SERVER,
78102
attributes=attributes,
79103
start_time=environ.get(

instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py

+42-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sys import modules
1616
from unittest.mock import patch
1717

18+
from django import VERSION
1819
from django.conf import settings
1920
from django.conf.urls import url
2021
from django.test import Client
@@ -28,14 +29,24 @@
2829
from opentelemetry.util import ExcludeList
2930

3031
# pylint: disable=import-error
31-
from .views import error, excluded, excluded_noarg, excluded_noarg2, traced
32+
from .views import (
33+
error,
34+
excluded,
35+
excluded_noarg,
36+
excluded_noarg2,
37+
route_span_name,
38+
traced,
39+
)
40+
41+
DJANGO_2_2 = VERSION >= (2, 2)
3242

3343
urlpatterns = [
3444
url(r"^traced/", traced),
3545
url(r"^error/", error),
3646
url(r"^excluded_arg/", excluded),
3747
url(r"^excluded_noarg/", excluded_noarg),
3848
url(r"^excluded_noarg2/", excluded_noarg2),
49+
url(r"^span_name/([0-9]{4})/$", route_span_name),
3950
]
4051
_django_instrumentor = DjangoInstrumentor()
4152

@@ -65,7 +76,9 @@ def test_traced_get(self):
6576

6677
span = spans[0]
6778

68-
self.assertEqual(span.name, "traced")
79+
self.assertEqual(
80+
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
81+
)
6982
self.assertEqual(span.kind, SpanKind.SERVER)
7083
self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK)
7184
self.assertEqual(span.attributes["http.method"], "GET")
@@ -84,7 +97,9 @@ def test_traced_post(self):
8497

8598
span = spans[0]
8699

87-
self.assertEqual(span.name, "traced")
100+
self.assertEqual(
101+
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
102+
)
88103
self.assertEqual(span.kind, SpanKind.SERVER)
89104
self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK)
90105
self.assertEqual(span.attributes["http.method"], "POST")
@@ -104,7 +119,9 @@ def test_error(self):
104119

105120
span = spans[0]
106121

107-
self.assertEqual(span.name, "error")
122+
self.assertEqual(
123+
span.name, "^error/" if DJANGO_2_2 else "tests.views.error"
124+
)
108125
self.assertEqual(span.kind, SpanKind.SERVER)
109126
self.assertEqual(
110127
span.status.canonical_code, StatusCanonicalCode.UNKNOWN
@@ -136,3 +153,24 @@ def test_exclude_lists(self):
136153
client.get("/excluded_noarg2/")
137154
span_list = self.memory_exporter.get_finished_spans()
138155
self.assertEqual(len(span_list), 1)
156+
157+
def test_span_name(self):
158+
Client().get("/span_name/1234/")
159+
span_list = self.memory_exporter.get_finished_spans()
160+
self.assertEqual(len(span_list), 1)
161+
162+
span = span_list[0]
163+
self.assertEqual(
164+
span.name,
165+
"^span_name/([0-9]{4})/$"
166+
if DJANGO_2_2
167+
else "tests.views.route_span_name",
168+
)
169+
170+
def test_span_name_404(self):
171+
Client().get("/span_name/1234567890/")
172+
span_list = self.memory_exporter.get_finished_spans()
173+
self.assertEqual(len(span_list), 1)
174+
175+
span = span_list[0]
176+
self.assertEqual(span.name, "HTTP GET")

instrumentation/opentelemetry-instrumentation-django/tests/views.py

+6
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ def excluded_noarg(request): # pylint: disable=unused-argument
1919

2020
def excluded_noarg2(request): # pylint: disable=unused-argument
2121
return HttpResponse()
22+
23+
24+
def route_span_name(
25+
request, *args, **kwargs
26+
): # pylint: disable=unused-argument
27+
return HttpResponse()

0 commit comments

Comments
 (0)