Django span names according to convention (#992)

This commit is contained in:
HiveTraum
2020-09-22 01:30:03 +05:00
committed by GitHub
parent cebfa2f3c3
commit 06511cd31a
4 changed files with 82 additions and 12 deletions

View File

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Changed span name extraction from request to comply semantic convention ([#992](https://github.com/open-telemetry/opentelemetry-python/pull/992))
## Version 0.13b0 ## Version 0.13b0
Released 2020-09-17 Released 2020-09-17

View File

@ -26,6 +26,14 @@ from opentelemetry.propagators import extract
from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace import SpanKind, get_tracer
from opentelemetry.util import ExcludeList from opentelemetry.util import ExcludeList
try:
from django.core.urlresolvers import ( # pylint: disable=no-name-in-module
resolve,
Resolver404,
)
except ImportError:
from django.urls import resolve, Resolver404
try: try:
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
except ImportError: except ImportError:
@ -50,17 +58,33 @@ class _DjangoMiddleware(MiddlewareMixin):
else: else:
_excluded_urls = ExcludeList(_excluded_urls) _excluded_urls = ExcludeList(_excluded_urls)
def process_view( @staticmethod
self, request, view_func, view_args, view_kwargs def _get_span_name(request):
): # pylint: disable=unused-argument try:
if getattr(request, "resolver_match"):
match = request.resolver_match
else:
match = resolve(request.get_full_path())
if hasattr(match, "route"):
return match.route
# Instead of using `view_name`, better to use `_func_name` as some applications can use similar
# view names in different modules
if hasattr(match, "_func_name"):
return match._func_name # pylint: disable=protected-access
# Fallback for safety as `_func_name` private field
return match.view_name
except Resolver404:
return "HTTP {}".format(request.method)
def process_request(self, request):
# request.META is a dictionary containing all available HTTP headers # request.META is a dictionary containing all available HTTP headers
# Read more about request.META here: # Read more about request.META here:
# https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META # https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META
# environ = {
# key.lower().replace('_', '-').replace("http-", "", 1): value
# for key, value in request.META.items()
# }
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
return return
@ -73,7 +97,7 @@ class _DjangoMiddleware(MiddlewareMixin):
attributes = collect_request_attributes(environ) attributes = collect_request_attributes(environ)
span = tracer.start_span( span = tracer.start_span(
view_func.__name__, self._get_span_name(request),
kind=SpanKind.SERVER, kind=SpanKind.SERVER,
attributes=attributes, attributes=attributes,
start_time=environ.get( start_time=environ.get(

View File

@ -15,6 +15,7 @@
from sys import modules from sys import modules
from unittest.mock import patch from unittest.mock import patch
from django import VERSION
from django.conf import settings from django.conf import settings
from django.conf.urls import url from django.conf.urls import url
from django.test import Client from django.test import Client
@ -28,7 +29,16 @@ from opentelemetry.trace.status import StatusCanonicalCode
from opentelemetry.util import ExcludeList from opentelemetry.util import ExcludeList
# pylint: disable=import-error # pylint: disable=import-error
from .views import error, excluded, excluded_noarg, excluded_noarg2, traced from .views import (
error,
excluded,
excluded_noarg,
excluded_noarg2,
route_span_name,
traced,
)
DJANGO_2_2 = VERSION >= (2, 2)
urlpatterns = [ urlpatterns = [
url(r"^traced/", traced), url(r"^traced/", traced),
@ -36,6 +46,7 @@ urlpatterns = [
url(r"^excluded_arg/", excluded), url(r"^excluded_arg/", excluded),
url(r"^excluded_noarg/", excluded_noarg), url(r"^excluded_noarg/", excluded_noarg),
url(r"^excluded_noarg2/", excluded_noarg2), url(r"^excluded_noarg2/", excluded_noarg2),
url(r"^span_name/([0-9]{4})/$", route_span_name),
] ]
_django_instrumentor = DjangoInstrumentor() _django_instrumentor = DjangoInstrumentor()
@ -65,7 +76,9 @@ class TestMiddleware(WsgiTestBase):
span = spans[0] span = spans[0]
self.assertEqual(span.name, "traced") self.assertEqual(
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
)
self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.kind, SpanKind.SERVER)
self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK)
self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.method"], "GET")
@ -84,7 +97,9 @@ class TestMiddleware(WsgiTestBase):
span = spans[0] span = spans[0]
self.assertEqual(span.name, "traced") self.assertEqual(
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
)
self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.kind, SpanKind.SERVER)
self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK)
self.assertEqual(span.attributes["http.method"], "POST") self.assertEqual(span.attributes["http.method"], "POST")
@ -104,7 +119,9 @@ class TestMiddleware(WsgiTestBase):
span = spans[0] span = spans[0]
self.assertEqual(span.name, "error") self.assertEqual(
span.name, "^error/" if DJANGO_2_2 else "tests.views.error"
)
self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.kind, SpanKind.SERVER)
self.assertEqual( self.assertEqual(
span.status.canonical_code, StatusCanonicalCode.UNKNOWN span.status.canonical_code, StatusCanonicalCode.UNKNOWN
@ -136,3 +153,24 @@ class TestMiddleware(WsgiTestBase):
client.get("/excluded_noarg2/") client.get("/excluded_noarg2/")
span_list = self.memory_exporter.get_finished_spans() span_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(span_list), 1) self.assertEqual(len(span_list), 1)
def test_span_name(self):
Client().get("/span_name/1234/")
span_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(span_list), 1)
span = span_list[0]
self.assertEqual(
span.name,
"^span_name/([0-9]{4})/$"
if DJANGO_2_2
else "tests.views.route_span_name",
)
def test_span_name_404(self):
Client().get("/span_name/1234567890/")
span_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(span_list), 1)
span = span_list[0]
self.assertEqual(span.name, "HTTP GET")

View File

@ -19,3 +19,9 @@ def excluded_noarg(request): # pylint: disable=unused-argument
def excluded_noarg2(request): # pylint: disable=unused-argument def excluded_noarg2(request): # pylint: disable=unused-argument
return HttpResponse() return HttpResponse()
def route_span_name(
request, *args, **kwargs
): # pylint: disable=unused-argument
return HttpResponse()