Skip to content

Commit

Permalink
Add Django ASGI support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
adamantike committed Sep 27, 2021
1 parent d2984f5 commit c12a600
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ install_requires =
opentelemetry-semantic-conventions == 0.24b0

[options.extras_require]
asgi =
opentelemetry-instrumentation-asgi == 0.22.dev0
test =
opentelemetry-test == 0.24b0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from opentelemetry.instrumentation.utils import extract_attributes_from_object
from opentelemetry.instrumentation.wsgi import (
add_response_attributes,
collect_request_attributes,
collect_request_attributes as wsgi_collect_request_attributes,
wsgi_getter,
)
from opentelemetry.propagate import extract
Expand Down Expand Up @@ -68,6 +68,25 @@ def __call__(self, request):
MiddlewareMixin = object


try:
from django.core.handlers.asgi import ASGIRequest
except ImportError:
ASGIRequest = None

try:
from opentelemetry.instrumentation.asgi import (
asgi_getter,
collect_request_attributes as asgi_collect_request_attributes,
set_status_code,
)
_is_asgi_supported = True
except ImportError:
asgi_getter = None
asgi_collect_request_attributes = None
set_status_code = None
_is_asgi_supported = False


_logger = getLogger(__name__)
_attributes_by_preference = [
[
Expand Down Expand Up @@ -132,6 +151,9 @@ def _get_span_name(request):
except Resolver404:
return "HTTP {}".format(request.method)

def _is_asgi_request(self, request):
return ASGIRequest and isinstance(request, ASGIRequest)

def process_request(self, request):
# request.META is a dictionary containing all available HTTP headers
# Read more about request.META here:
Expand All @@ -140,12 +162,23 @@ def process_request(self, request):
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
return

is_asgi_request = self._is_asgi_request(request)
if is_asgi_request and not _is_asgi_supported:
return

# pylint:disable=W0212
request._otel_start_time = time()

request_meta = request.META

token = attach(extract(request_meta, getter=wsgi_getter))
if is_asgi_request:
carrier_getter = asgi_getter
collect_request_attributes = asgi_collect_request_attributes
else:
carrier_getter = wsgi_getter
collect_request_attributes = wsgi_collect_request_attributes

token = attach(extract(request_meta, getter=carrier_getter))

span = self._tracer.start_span(
self._get_span_name(request),
Expand Down Expand Up @@ -207,15 +240,22 @@ def process_response(self, request, response):
if self._excluded_urls.url_disabled(request.build_absolute_uri("?")):
return response

is_asgi_request = self._is_asgi_request(request)
if is_asgi_request and not _is_asgi_supported:
return

activation = request.META.pop(self._environ_activation_key, None)
span = request.META.pop(self._environ_span_key, None)

if activation and span:
add_response_attributes(
span,
"{} {}".format(response.status_code, response.reason_phrase),
response,
)
if is_asgi_request:
set_status_code(request.META[self._environ_span_key], response.status_code)
else:
add_response_attributes(
span,
"{} {}".format(response.status_code, response.reason_phrase),
response,
)

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

if self._environ_token in request.META.keys():
detach(request.environ.get(self._environ_token))
if is_asgi_request:
detach(request.META.get(self._environ_token))
else:
detach(request.environ.get(self._environ_token))
request.META.pop(self._environ_token)

return response
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ commands_pre =

falcon{2,3},flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,urllib3,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
wsgi,falcon{2,3},flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test]
asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]
asgi,django,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]

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

Expand Down

0 comments on commit c12a600

Please sign in to comment.