Skip to content

Commit 193f591

Browse files
authored
feat(django): Instrument views as spans (#787)
1 parent 44cc08e commit 193f591

File tree

5 files changed

+97
-34
lines changed

5 files changed

+97
-34
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from sentry_sdk.integrations.django.transactions import LEGACY_RESOLVER
4040
from sentry_sdk.integrations.django.templates import get_template_frame_from_exception
4141
from sentry_sdk.integrations.django.middleware import patch_django_middlewares
42+
from sentry_sdk.integrations.django.views import patch_resolver
4243

4344

4445
if MYPY:
@@ -199,6 +200,7 @@ def _django_queryset_repr(value, hint):
199200

200201
_patch_channels()
201202
patch_django_middlewares()
203+
patch_resolver()
202204

203205

204206
_DRF_PATCHED = False
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import functools
2+
3+
from sentry_sdk.hub import Hub
4+
from sentry_sdk._types import MYPY
5+
6+
if MYPY:
7+
from typing import Any
8+
9+
from django.urls.resolvers import ResolverMatch
10+
11+
12+
def patch_resolver():
13+
# type: () -> None
14+
try:
15+
from django.urls.resolvers import URLResolver
16+
except ImportError:
17+
try:
18+
from django.urls.resolvers import RegexURLResolver as URLResolver
19+
except ImportError:
20+
from django.core.urlresolvers import RegexURLResolver as URLResolver
21+
22+
from sentry_sdk.integrations.django import DjangoIntegration
23+
24+
old_resolve = URLResolver.resolve
25+
26+
def resolve(self, path):
27+
# type: (URLResolver, Any) -> ResolverMatch
28+
hub = Hub.current
29+
integration = hub.get_integration(DjangoIntegration)
30+
31+
if integration is None or not integration.middleware_spans:
32+
return old_resolve(self, path)
33+
34+
return _wrap_resolver_match(hub, old_resolve(self, path))
35+
36+
URLResolver.resolve = resolve
37+
38+
39+
def _wrap_resolver_match(hub, resolver_match):
40+
# type: (Hub, ResolverMatch) -> ResolverMatch
41+
42+
# XXX: The wrapper function is created for every request. Find more
43+
# efficient way to wrap views (or build a cache?)
44+
45+
old_callback = resolver_match.func
46+
47+
@functools.wraps(old_callback)
48+
def callback(*args, **kwargs):
49+
# type: (*Any, **Any) -> Any
50+
with hub.start_span(op="django.view", description=resolver_match.view_name):
51+
return old_callback(*args, **kwargs)
52+
53+
resolver_match.func = callback
54+
55+
return resolver_match

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ def inner(event):
334334
by_parent.setdefault(span["parent_span_id"], []).append(span)
335335

336336
def render_span(span):
337-
yield "- op={!r}: description={!r}".format(
338-
span.get("op"), span.get("description")
337+
yield "- op={}: description={}".format(
338+
json.dumps(span.get("op")), json.dumps(span.get("description"))
339339
)
340340
for subspan in by_parent.get(span["span_id"]) or ():
341341
for line in render_span(subspan):

tests/integrations/django/test_basic.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ def test_does_not_capture_403(sentry_init, client, capture_events, endpoint):
511511
assert not events
512512

513513

514-
def test_middleware_spans(sentry_init, client, capture_events):
514+
def test_middleware_spans(sentry_init, client, capture_events, render_span_tree):
515515
sentry_init(
516516
integrations=[DjangoIntegration()],
517517
traces_sample_rate=1.0,
@@ -525,26 +525,32 @@ def test_middleware_spans(sentry_init, client, capture_events):
525525

526526
assert message["message"] == "hi"
527527

528-
for middleware in transaction["spans"]:
529-
assert middleware["op"] == "django.middleware"
530-
531528
if DJANGO_VERSION >= (1, 10):
532-
reference_value = [
533-
"django.contrib.sessions.middleware.SessionMiddleware.__call__",
534-
"django.contrib.auth.middleware.AuthenticationMiddleware.__call__",
535-
"tests.integrations.django.myapp.settings.TestMiddleware.__call__",
536-
"tests.integrations.django.myapp.settings.TestFunctionMiddleware.__call__",
537-
]
538-
else:
539-
reference_value = [
540-
"django.contrib.sessions.middleware.SessionMiddleware.process_request",
541-
"django.contrib.auth.middleware.AuthenticationMiddleware.process_request",
542-
"tests.integrations.django.myapp.settings.TestMiddleware.process_request",
543-
"tests.integrations.django.myapp.settings.TestMiddleware.process_response",
544-
"django.contrib.sessions.middleware.SessionMiddleware.process_response",
545-
]
529+
assert (
530+
render_span_tree(transaction)
531+
== """\
532+
- op="http.server": description=null
533+
- op="django.middleware": description="django.contrib.sessions.middleware.SessionMiddleware.__call__"
534+
- op="django.middleware": description="django.contrib.auth.middleware.AuthenticationMiddleware.__call__"
535+
- op="django.middleware": description="tests.integrations.django.myapp.settings.TestMiddleware.__call__"
536+
- op="django.middleware": description="tests.integrations.django.myapp.settings.TestFunctionMiddleware.__call__"
537+
- op="django.view": description="message"\
538+
"""
539+
)
546540

547-
assert [t["description"] for t in transaction["spans"]] == reference_value
541+
else:
542+
assert (
543+
render_span_tree(transaction)
544+
== """\
545+
- op="http.server": description=null
546+
- op="django.middleware": description="django.contrib.sessions.middleware.SessionMiddleware.process_request"
547+
- op="django.middleware": description="django.contrib.auth.middleware.AuthenticationMiddleware.process_request"
548+
- op="django.middleware": description="tests.integrations.django.myapp.settings.TestMiddleware.process_request"
549+
- op="django.view": description="message"
550+
- op="django.middleware": description="tests.integrations.django.myapp.settings.TestMiddleware.process_response"
551+
- op="django.middleware": description="django.contrib.sessions.middleware.SessionMiddleware.process_response"\
552+
"""
553+
)
548554

549555

550556
def test_middleware_spans_disabled(sentry_init, client, capture_events):

tests/integrations/sqlalchemy/test_sqlalchemy.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,19 @@ class Address(Base):
120120
assert (
121121
render_span_tree(event)
122122
== """\
123-
- op=None: description=None
124-
- op='db': description='SAVEPOINT sa_savepoint_1'
125-
- op='db': description='SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?'
126-
- op='db': description='RELEASE SAVEPOINT sa_savepoint_1'
127-
- op='db': description='SAVEPOINT sa_savepoint_2'
128-
- op='db': description='INSERT INTO person (id, name) VALUES (?, ?)'
129-
- op='db': description='ROLLBACK TO SAVEPOINT sa_savepoint_2'
130-
- op='db': description='SAVEPOINT sa_savepoint_3'
131-
- op='db': description='INSERT INTO person (id, name) VALUES (?, ?)'
132-
- op='db': description='ROLLBACK TO SAVEPOINT sa_savepoint_3'
133-
- op='db': description='SAVEPOINT sa_savepoint_4'
134-
- op='db': description='SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?'
135-
- op='db': description='RELEASE SAVEPOINT sa_savepoint_4'\
123+
- op=null: description=null
124+
- op="db": description="SAVEPOINT sa_savepoint_1"
125+
- op="db": description="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?"
126+
- op="db": description="RELEASE SAVEPOINT sa_savepoint_1"
127+
- op="db": description="SAVEPOINT sa_savepoint_2"
128+
- op="db": description="INSERT INTO person (id, name) VALUES (?, ?)"
129+
- op="db": description="ROLLBACK TO SAVEPOINT sa_savepoint_2"
130+
- op="db": description="SAVEPOINT sa_savepoint_3"
131+
- op="db": description="INSERT INTO person (id, name) VALUES (?, ?)"
132+
- op="db": description="ROLLBACK TO SAVEPOINT sa_savepoint_3"
133+
- op="db": description="SAVEPOINT sa_savepoint_4"
134+
- op="db": description="SELECT person.id AS person_id, person.name AS person_name \\nFROM person\\n LIMIT ? OFFSET ?"
135+
- op="db": description="RELEASE SAVEPOINT sa_savepoint_4"\
136136
"""
137137
)
138138

0 commit comments

Comments
 (0)