Skip to content

Commit 46e4b40

Browse files
mergify[bot]Kyle-Verhoogbrettlangdon
authored
Add ASGI support to Django (#2656) (#2752)
* Add ASGI support to Django Using the ASGI middleware we can relatively cleanly add async/ASGI support for Django. The previous method of instrumentation was first attempted but it was decided against for a couple reasons: - Tracing the ASGI application would require more than just wrapping the get_response_async method (although this would provide most of the visibility desired) as we'd need to trace the invocation of the response method. - We already have an ASGI middleware implementation. * Store span in ASGI scope * Don't override distributed tracing setting It gets reused by the ASGI middleware * Don't wrap the asgi application when get_response is used for async * Update ddtrace/contrib/django/patch.py Co-authored-by: Tahir H. Butt <[email protected]> Co-authored-by: Brett Langdon <[email protected]> (cherry picked from commit 71ca68d) # Conflicts: # tests/contrib/django/asgi.py # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_200_30.snap # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_200_31.snap # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_200_3x.snap # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_30.snap # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_31.snap # tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_3x.snap Co-authored-by: Kyle Verhoog <[email protected]> Co-authored-by: Brett Langdon <[email protected]>
1 parent bf60fdd commit 46e4b40

12 files changed

+1720
-0
lines changed

ddtrace/contrib/django/_asgi.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Module providing async hooks. Do not import this module unless using Python >= 3.6.
3+
"""
4+
from ddtrace.contrib.asgi import span_from_scope
5+
6+
from .. import trace_utils
7+
from ...utils import get_argument_value
8+
from .utils import _after_request_tags
9+
from .utils import _before_request_tags
10+
11+
12+
@trace_utils.with_traced_module
13+
async def traced_get_response_async(django, pin, func, instance, args, kwargs):
14+
"""Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations).
15+
16+
This is the main entry point for requests.
17+
18+
Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler).
19+
This method invokes the middleware chain and returns the response generated by the chain.
20+
"""
21+
request = get_argument_value(args, kwargs, 0, "request")
22+
span = span_from_scope(request.scope)
23+
if span is None:
24+
return await func(*args, **kwargs)
25+
26+
_before_request_tags(pin, span, request)
27+
response = await func(*args, **kwargs)
28+
_after_request_tags(pin, span, request, response)
29+
return response

ddtrace/contrib/django/patch.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,16 @@ def traced_as_view(django, pin, func, instance, args, kwargs):
422422
return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(view)))
423423

424424

425+
@trace_utils.with_traced_module
426+
def traced_get_asgi_application(django, pin, func, instance, args, kwargs):
427+
from ddtrace.contrib.asgi import TraceMiddleware
428+
429+
def django_asgi_modifier(span, scope):
430+
span.name = "django.request"
431+
432+
return TraceMiddleware(func(*args, **kwargs), integration_config=config.django, span_modifier=django_asgi_modifier)
433+
434+
425435
def _patch(django):
426436
Pin().onto(django)
427437
trace_utils.wrap(django, "apps.registry.Apps.populate", traced_populate(django))
@@ -434,6 +444,21 @@ def _patch(django):
434444
trace_utils.wrap(django, "core.handlers.base.BaseHandler.load_middleware", traced_load_middleware(django))
435445

436446
trace_utils.wrap(django, "core.handlers.base.BaseHandler.get_response", traced_get_response(django))
447+
if hasattr(django.core.handlers.base.BaseHandler, "get_response_async"):
448+
# Have to inline this import as the module contains syntax incompatible with Python 3.5 and below
449+
from ._asgi import traced_get_response_async
450+
451+
trace_utils.wrap(django, "core.handlers.base.BaseHandler.get_response_async", traced_get_response_async(django))
452+
453+
# Only wrap get_asgi_application if get_response_async exists. Otherwise we will effectively double-patch
454+
# because get_response and get_asgi_application will be used.
455+
if "django.core.asgi" not in sys.modules:
456+
try:
457+
import django.core.asgi
458+
except ImportError:
459+
pass
460+
else:
461+
trace_utils.wrap(django, "core.asgi.get_asgi_application", traced_get_asgi_application(django))
437462

438463
# DEV: this check will be replaced with import hooks in the future
439464
if "django.template.base" not in sys.modules:
@@ -476,6 +501,7 @@ def _unpatch(django):
476501
trace_utils.unwrap(django.apps.registry.Apps, "populate")
477502
trace_utils.unwrap(django.core.handlers.base.BaseHandler, "load_middleware")
478503
trace_utils.unwrap(django.core.handlers.base.BaseHandler, "get_response")
504+
trace_utils.unwrap(django.core.handlers.base.BaseHandler, "get_response_async")
479505
trace_utils.unwrap(django.template.base.Template, "render")
480506
trace_utils.unwrap(django.conf.urls.static, "static")
481507
trace_utils.unwrap(django.conf.urls, "url")

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ botocore
2929
CGroup
3030
cgroups
3131
cherrypy
32+
codepath
3233
compat
3334
composable
3435
config
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
Django: add support for version 3.1+ ASGI applications. A different
5+
codepath is taken for requests starting in Django 3.1 which led to the top
6+
level span not being generated for requests. The fix introduces automatic
7+
installation of the ASGI middleware to trace Django requests.

tests/contrib/django/asgi.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from django.core.asgi import get_asgi_application
2+
3+
4+
application = get_asgi_application()

tests/contrib/django/django1_app/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
ALLOWED_HOSTS = [
55
"testserver",
6+
"localhost",
67
]
78

89
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
[[{"name" "django.request"
2+
"service" "django"
3+
"resource" "GET ^$"
4+
"type" "web"
5+
"error" 0
6+
"span_id" 0
7+
"trace_id" 0
8+
"parent_id" nil
9+
"start" 1628305961836247598
10+
"duration" 1127994
11+
"meta" {"runtime-id" "060d88f83f144c3e9bce0c7a5ec11dff"
12+
"http.route" "^$"
13+
"http.url" "http://localhost:8000/"
14+
"django.user.is_authenticated" "False"
15+
"http.status_code" "200"
16+
"http.method" "GET"
17+
"django.response.class" "django.http.response.HttpResponse"
18+
"django.request.class" "django.core.handlers.asgi.ASGIRequest"
19+
"django.view" "tests.contrib.django.views.index"}
20+
"metrics" {"_dd.agent_psr" 1.0
21+
"_dd.measured" 1
22+
"_sampling_priority_v1" 1
23+
"system.pid" 621
24+
"_dd.tracer_kr" 1.0}}
25+
{"name" "django.middleware"
26+
"service" "django"
27+
"resource" "django.contrib.sessions.middleware.SessionMiddleware.__call__"
28+
"error" 0
29+
"span_id" 1
30+
"trace_id" 0
31+
"parent_id" 0
32+
"start" 1628305961836337443
33+
"duration" 937203}
34+
{"name" "django.middleware"
35+
"service" "django"
36+
"resource" "django.contrib.sessions.middleware.SessionMiddleware.process_request"
37+
"error" 0
38+
"span_id" 2
39+
"trace_id" 0
40+
"parent_id" 1
41+
"start" 1628305961836361975
42+
"duration" 42649}
43+
{"name" "django.middleware"
44+
"service" "django"
45+
"resource" "django.middleware.common.CommonMiddleware.__call__"
46+
"error" 0
47+
"span_id" 3
48+
"trace_id" 0
49+
"parent_id" 1
50+
"start" 1628305961836427466
51+
"duration" 807382}
52+
{"name" "django.middleware"
53+
"service" "django"
54+
"resource" "django.middleware.common.CommonMiddleware.process_request"
55+
"error" 0
56+
"span_id" 5
57+
"trace_id" 0
58+
"parent_id" 3
59+
"start" 1628305961836451290
60+
"duration" 35622}
61+
{"name" "django.middleware"
62+
"service" "django"
63+
"resource" "django.middleware.csrf.CsrfViewMiddleware.__call__"
64+
"error" 0
65+
"span_id" 6
66+
"trace_id" 0
67+
"parent_id" 3
68+
"start" 1628305961836509197
69+
"duration" 666824}
70+
{"name" "django.middleware"
71+
"service" "django"
72+
"resource" "django.middleware.csrf.CsrfViewMiddleware.process_request"
73+
"error" 0
74+
"span_id" 8
75+
"trace_id" 0
76+
"parent_id" 6
77+
"start" 1628305961836532117
78+
"duration" 12993}
79+
{"name" "django.middleware"
80+
"service" "django"
81+
"resource" "django.contrib.auth.middleware.AuthenticationMiddleware.__call__"
82+
"error" 0
83+
"span_id" 9
84+
"trace_id" 0
85+
"parent_id" 6
86+
"start" 1628305961836563811
87+
"duration" 576969}
88+
{"name" "django.middleware"
89+
"service" "django"
90+
"resource" "django.contrib.auth.middleware.AuthenticationMiddleware.process_request"
91+
"error" 0
92+
"span_id" 11
93+
"trace_id" 0
94+
"parent_id" 9
95+
"start" 1628305961836581723
96+
"duration" 12960}
97+
{"name" "django.middleware"
98+
"service" "django"
99+
"resource" "django.contrib.messages.middleware.MessageMiddleware.__call__"
100+
"error" 0
101+
"span_id" 12
102+
"trace_id" 0
103+
"parent_id" 9
104+
"start" 1628305961836647652
105+
"duration" 486482}
106+
{"name" "django.middleware"
107+
"service" "django"
108+
"resource" "django.contrib.messages.middleware.MessageMiddleware.process_request"
109+
"error" 0
110+
"span_id" 13
111+
"trace_id" 0
112+
"parent_id" 12
113+
"start" 1628305961836668150
114+
"duration" 40241}
115+
{"name" "django.middleware"
116+
"service" "django"
117+
"resource" "django.middleware.clickjacking.XFrameOptionsMiddleware.__call__"
118+
"error" 0
119+
"span_id" 14
120+
"trace_id" 0
121+
"parent_id" 12
122+
"start" 1628305961836726914
123+
"duration" 374564}
124+
{"name" "django.middleware"
125+
"service" "django"
126+
"resource" "django.middleware.security.SecurityMiddleware.__call__"
127+
"error" 0
128+
"span_id" 16
129+
"trace_id" 0
130+
"parent_id" 14
131+
"start" 1628305961836744841
132+
"duration" 320451}
133+
{"name" "django.middleware"
134+
"service" "django"
135+
"resource" "django.middleware.security.SecurityMiddleware.process_request"
136+
"error" 0
137+
"span_id" 18
138+
"trace_id" 0
139+
"parent_id" 16
140+
"start" 1628305961836762208
141+
"duration" 9435}
142+
{"name" "django.middleware"
143+
"service" "django"
144+
"resource" "tests.contrib.django.middleware.ClsMiddleware.__call__"
145+
"error" 0
146+
"span_id" 19
147+
"trace_id" 0
148+
"parent_id" 16
149+
"start" 1628305961836788263
150+
"duration" 239310}
151+
{"name" "django.middleware"
152+
"service" "django"
153+
"resource" "tests.contrib.django.middleware.EverythingMiddleware"
154+
"error" 0
155+
"span_id" 21
156+
"trace_id" 0
157+
"parent_id" 19
158+
"start" 1628305961836810020
159+
"duration" 211352}
160+
{"name" "django.middleware"
161+
"service" "django"
162+
"resource" "tests.contrib.django.middleware.EverythingMiddleware.__call__"
163+
"error" 0
164+
"span_id" 22
165+
"trace_id" 0
166+
"parent_id" 21
167+
"start" 1628305961836827926
168+
"duration" 186624}
169+
{"name" "django.middleware"
170+
"service" "django"
171+
"resource" "django.middleware.csrf.CsrfViewMiddleware.process_view"
172+
"error" 0
173+
"span_id" 23
174+
"trace_id" 0
175+
"parent_id" 22
176+
"start" 1628305961836882297
177+
"duration" 13731}
178+
{"name" "django.middleware"
179+
"service" "django"
180+
"resource" "tests.contrib.django.middleware.EverythingMiddleware.process_view"
181+
"error" 0
182+
"span_id" 24
183+
"trace_id" 0
184+
"parent_id" 22
185+
"start" 1628305961836911198
186+
"duration" 8737}
187+
{"name" "django.view"
188+
"service" "django"
189+
"resource" "tests.contrib.django.views.index"
190+
"error" 0
191+
"span_id" 25
192+
"trace_id" 0
193+
"parent_id" 22
194+
"start" 1628305961836973967
195+
"duration" 32018}
196+
{"name" "django.middleware"
197+
"service" "django"
198+
"resource" "django.middleware.security.SecurityMiddleware.process_response"
199+
"error" 0
200+
"span_id" 20
201+
"trace_id" 0
202+
"parent_id" 16
203+
"start" 1628305961837043983
204+
"duration" 14052}
205+
{"name" "django.middleware"
206+
"service" "django"
207+
"resource" "django.middleware.clickjacking.XFrameOptionsMiddleware.process_response"
208+
"error" 0
209+
"span_id" 17
210+
"trace_id" 0
211+
"parent_id" 14
212+
"start" 1628305961837080944
213+
"duration" 13470}
214+
{"name" "django.middleware"
215+
"service" "django"
216+
"resource" "django.contrib.messages.middleware.MessageMiddleware.process_response"
217+
"error" 0
218+
"span_id" 15
219+
"trace_id" 0
220+
"parent_id" 12
221+
"start" 1628305961837117063
222+
"duration" 10180}
223+
{"name" "django.middleware"
224+
"service" "django"
225+
"resource" "django.middleware.csrf.CsrfViewMiddleware.process_response"
226+
"error" 0
227+
"span_id" 10
228+
"trace_id" 0
229+
"parent_id" 6
230+
"start" 1628305961837158818
231+
"duration" 10019}
232+
{"name" "django.middleware"
233+
"service" "django"
234+
"resource" "django.middleware.common.CommonMiddleware.process_response"
235+
"error" 0
236+
"span_id" 7
237+
"trace_id" 0
238+
"parent_id" 3
239+
"start" 1628305961837191878
240+
"duration" 34778}
241+
{"name" "django.middleware"
242+
"service" "django"
243+
"resource" "django.contrib.sessions.middleware.SessionMiddleware.process_response"
244+
"error" 0
245+
"span_id" 4
246+
"trace_id" 0
247+
"parent_id" 1
248+
"start" 1628305961837252585
249+
"duration" 14738}]]

0 commit comments

Comments
 (0)