Skip to content

Commit 00eac94

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 bddd082 commit 00eac94

File tree

3 files changed

+55
-10
lines changed

3 files changed

+55
-10
lines changed

instrumentation/opentelemetry-instrumentation-django/setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ install_requires =
4545
opentelemetry-semantic-conventions == 0.23.dev0
4646

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

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

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from opentelemetry.instrumentation.utils import extract_attributes_from_object
2828
from opentelemetry.instrumentation.wsgi import (
2929
add_response_attributes,
30-
collect_request_attributes,
30+
collect_request_attributes as wsgi_collect_request_attributes,
3131
wsgi_getter,
3232
)
3333
from opentelemetry.propagate import extract
@@ -69,6 +69,25 @@ def __call__(self, request):
6969
MiddlewareMixin = object
7070

7171

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

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

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

147173
request_meta = request.META
148174

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

151184
span = self._tracer.start_span(
152185
self._get_span_name(request),
@@ -208,15 +241,22 @@ def process_response(self, request, response):
208241
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
209242
return response
210243

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

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

221261
propagator = get_global_response_propagator()
222262
if propagator:
@@ -239,7 +279,10 @@ def process_response(self, request, response):
239279
activation.__exit__(None, None, None)
240280

241281
if self._environ_token in request.META.keys():
242-
detach(request.environ.get(self._environ_token))
282+
if is_asgi_request:
283+
detach(request.META.get(self._environ_token))
284+
else:
285+
detach(request.environ.get(self._environ_token))
243286
request.META.pop(self._environ_token)
244287

245288
return response

tox.ini

Lines changed: 2 additions & 2 deletions
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

@@ -335,7 +335,7 @@ commands =
335335

336336
[testenv:lint]
337337
basepython: python3.9
338-
recreate = False
338+
recreate = False
339339
deps =
340340
-c dev-requirements.txt
341341
flaky

0 commit comments

Comments
 (0)