Skip to content

Commit 57c7dd5

Browse files
committed
WIP: Add Django ASGI support
Still a WIP, 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 a946d5c commit 57c7dd5

File tree

3 files changed

+55
-10
lines changed

3 files changed

+55
-10
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-api == 1.0.0
4646

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

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

+51-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from opentelemetry.instrumentation.utils import extract_attributes_from_object
2121
from opentelemetry.instrumentation.wsgi import (
2222
add_response_attributes,
23-
collect_request_attributes,
23+
collect_request_attributes as wsgi_collect_request_attributes,
2424
wsgi_getter,
2525
)
2626
from opentelemetry.propagate import extract
@@ -40,6 +40,25 @@
4040
except ImportError:
4141
MiddlewareMixin = object
4242

43+
try:
44+
from django.core.handlers.asgi import ASGIRequest
45+
except ImportError:
46+
ASGIRequest = None
47+
48+
try:
49+
from opentelemetry.instrumentation.asgi import (
50+
asgi_getter,
51+
collect_request_attributes as asgi_collect_request_attributes,
52+
set_status_code,
53+
)
54+
_is_asgi_supported = True
55+
except ImportError:
56+
asgi_getter = None
57+
asgi_collect_request_attributes = None
58+
set_status_code = None
59+
_is_asgi_supported = False
60+
61+
4362
_logger = getLogger(__name__)
4463
_attributes_by_preference = [
4564
["http.scheme", "http.host", "http.target"],
@@ -84,6 +103,9 @@ def _get_span_name(request):
84103
except Resolver404:
85104
return "HTTP {}".format(request.method)
86105

106+
def _is_asgi_request(self, request):
107+
return ASGIRequest and isinstance(request, ASGIRequest)
108+
87109
def process_request(self, request):
88110
# request.META is a dictionary containing all available HTTP headers
89111
# Read more about request.META here:
@@ -92,12 +114,23 @@ def process_request(self, request):
92114
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
93115
return
94116

117+
is_asgi_request = self._is_asgi_request(request)
118+
if is_asgi_request and not _is_asgi_supported:
119+
return
120+
95121
# pylint:disable=W0212
96122
request._otel_start_time = time()
97123

98124
request_meta = request.META
99125

100-
token = attach(extract(request_meta, getter=wsgi_getter))
126+
if is_asgi_request:
127+
carrier_getter = asgi_getter
128+
collect_request_attributes = asgi_collect_request_attributes
129+
else:
130+
carrier_getter = wsgi_getter
131+
collect_request_attributes = wsgi_collect_request_attributes
132+
133+
token = attach(extract(request_meta, getter=carrier_getter))
101134

102135
tracer = get_tracer(__name__, __version__)
103136

@@ -156,15 +189,22 @@ def process_response(self, request, response):
156189
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
157190
return response
158191

192+
is_asgi_request = self._is_asgi_request(request)
193+
if is_asgi_request and not _is_asgi_supported:
194+
return
195+
159196
if (
160197
self._environ_activation_key in request.META.keys()
161198
and self._environ_span_key in request.META.keys()
162199
):
163-
add_response_attributes(
164-
request.META[self._environ_span_key],
165-
"{} {}".format(response.status_code, response.reason_phrase),
166-
response,
167-
)
200+
if is_asgi_request:
201+
set_status_code(request.META[self._environ_span_key], response.status_code)
202+
else:
203+
add_response_attributes(
204+
request.META[self._environ_span_key],
205+
"{} {}".format(response.status_code, response.reason_phrase),
206+
response,
207+
)
168208

169209
request.META.pop(self._environ_span_key)
170210

@@ -182,7 +222,10 @@ def process_response(self, request, response):
182222
request.META.pop(self._environ_activation_key)
183223

184224
if self._environ_token in request.META.keys():
185-
detach(request.environ.get(self._environ_token))
225+
if is_asgi_request:
226+
detach(request.META.get(self._environ_token))
227+
else:
228+
detach(request.environ.get(self._environ_token))
186229
request.META.pop(self._environ_token)
187230

188231
return response

Diff for: tox.ini

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ commands_pre =
226226

227227
falcon,flask,django,pyramid,tornado,starlette,fastapi: pip install {toxinidir}/util/opentelemetry-util-http
228228
wsgi,falcon,flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi
229-
asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi
229+
asgi,django,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi
230230

231231
asyncpg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg
232232

@@ -319,7 +319,7 @@ commands =
319319

320320
[testenv:lint]
321321
basepython: python3.8
322-
recreate = False
322+
recreate = False
323323
deps =
324324
-c dev-requirements.txt
325325
flaky

0 commit comments

Comments
 (0)