Skip to content

Commit 0456740

Browse files
committed
Add Django ASGI support
This diff adds `asgi` as an extra, and uses its methods if the current request is an `ASGIRequest`. I still need to dig deeper in the current test suite, to find a way to duplicate the tests in `instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py`, but using an [`AsyncClient`](https://docs.djangoproject.com/en/3.1/topics/testing/tools/#testing-asynchronous-code). Fixes open-telemetry#165, open-telemetry#185, open-telemetry#280, open-telemetry#334.
1 parent 50e7b1b commit 0456740

File tree

3 files changed

+54
-9
lines changed

3 files changed

+54
-9
lines changed

Diff for: instrumentation/opentelemetry-instrumentation-django/setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ install_requires =
4545
opentelemetry-semantic-conventions == 0.24b0
4646

4747
[options.extras_require]
48+
asgi =
49+
opentelemetry-instrumentation-asgi == 0.22.dev0
4850
test =
4951
opentelemetry-test == 0.24b0
5052

Diff for: instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py

+51-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from opentelemetry.instrumentation.utils import extract_attributes_from_object
2727
from opentelemetry.instrumentation.wsgi import (
2828
add_response_attributes,
29-
collect_request_attributes,
29+
collect_request_attributes as wsgi_collect_request_attributes,
3030
wsgi_getter,
3131
)
3232
from opentelemetry.propagate import extract
@@ -68,6 +68,25 @@ def __call__(self, request):
6868
MiddlewareMixin = object
6969

7070

71+
try:
72+
from django.core.handlers.asgi import ASGIRequest
73+
except ImportError:
74+
ASGIRequest = None
75+
76+
try:
77+
from opentelemetry.instrumentation.asgi import (
78+
asgi_getter,
79+
collect_request_attributes as asgi_collect_request_attributes,
80+
set_status_code,
81+
)
82+
_is_asgi_supported = True
83+
except ImportError:
84+
asgi_getter = None
85+
asgi_collect_request_attributes = None
86+
set_status_code = None
87+
_is_asgi_supported = False
88+
89+
7190
_logger = getLogger(__name__)
7291
_attributes_by_preference = [
7392
[
@@ -132,6 +151,9 @@ def _get_span_name(request):
132151
except Resolver404:
133152
return "HTTP {}".format(request.method)
134153

154+
def _is_asgi_request(self, request):
155+
return ASGIRequest and isinstance(request, ASGIRequest)
156+
135157
def process_request(self, request):
136158
# request.META is a dictionary containing all available HTTP headers
137159
# Read more about request.META here:
@@ -140,12 +162,23 @@ def process_request(self, request):
140162
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
141163
return
142164

165+
is_asgi_request = self._is_asgi_request(request)
166+
if is_asgi_request and not _is_asgi_supported:
167+
return
168+
143169
# pylint:disable=W0212
144170
request._otel_start_time = time()
145171

146172
request_meta = request.META
147173

148-
token = attach(extract(request_meta, getter=wsgi_getter))
174+
if is_asgi_request:
175+
carrier_getter = asgi_getter
176+
collect_request_attributes = asgi_collect_request_attributes
177+
else:
178+
carrier_getter = wsgi_getter
179+
collect_request_attributes = wsgi_collect_request_attributes
180+
181+
token = attach(extract(request_meta, getter=carrier_getter))
149182

150183
span = self._tracer.start_span(
151184
self._get_span_name(request),
@@ -207,15 +240,22 @@ def process_response(self, request, response):
207240
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
208241
return response
209242

243+
is_asgi_request = self._is_asgi_request(request)
244+
if is_asgi_request and not _is_asgi_supported:
245+
return
246+
210247
activation = request.META.pop(self._environ_activation_key, None)
211248
span = request.META.pop(self._environ_span_key, None)
212249

213250
if activation and span:
214-
add_response_attributes(
215-
span,
216-
"{} {}".format(response.status_code, response.reason_phrase),
217-
response,
218-
)
251+
if is_asgi_request:
252+
set_status_code(request.META[self._environ_span_key], response.status_code)
253+
else:
254+
add_response_attributes(
255+
span,
256+
"{} {}".format(response.status_code, response.reason_phrase),
257+
response,
258+
)
219259

220260
propagator = get_global_response_propagator()
221261
if propagator:
@@ -238,7 +278,10 @@ def process_response(self, request, response):
238278
activation.__exit__(None, None, None)
239279

240280
if self._environ_token in request.META.keys():
241-
detach(request.environ.get(self._environ_token))
281+
if is_asgi_request:
282+
detach(request.META.get(self._environ_token))
283+
else:
284+
detach(request.environ.get(self._environ_token))
242285
request.META.pop(self._environ_token)
243286

244287
return response

Diff for: tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ commands_pre =
238238

239239
falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
240240
wsgi,falcon,flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test]
241-
asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]
241+
asgi,django,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]
242242

243243
asyncpg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg[test]
244244

0 commit comments

Comments
 (0)