Skip to content

Commit

Permalink
WIP: Add Django ASGI support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
adamantike committed May 30, 2021
1 parent 4a8b32b commit 2fb9168
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ install_requires =
opentelemetry-semantic-conventions == 0.22.dev0

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

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 All @@ -47,6 +47,25 @@
except ImportError:
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 @@ -111,6 +130,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 @@ -119,12 +141,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 @@ -186,15 +219,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 @@ -217,7 +257,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
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ commands_pre =

falcon,flask,django,pyramid,tornado,starlette,fastapi: pip install {toxinidir}/util/opentelemetry-util-http[test]
wsgi,falcon,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 Expand Up @@ -328,7 +328,7 @@ commands =

[testenv:lint]
basepython: python3.8
recreate = False
recreate = False
deps =
-c dev-requirements.txt
flaky
Expand Down

0 comments on commit 2fb9168

Please sign in to comment.