Skip to content

Commit

Permalink
[Django] Added support for traceresponse headers
Browse files Browse the repository at this point in the history
Added opt-in support to return traceresponse headers from Django.

This allows users to configure their Django apps to inject trace context
as headers in HTTP responses. This is useful when client side apps need
to connect their spans with the server side spans e.g, in RUM products.

Today the most practical way to do this is to use the `Server-Timing`
header but in near future we might use the `traceresponse` header as
described here: https://w3c.github.io/trace-context/#trace-context-http-response-headers-format

As a result the implementation does not use a hard-coded header and
instead let's the users pick one.

This can be done by setting the `OTEL_PYTHON_TRACE_RESPONSE_HEADER` to
the header name that users want to inject in HTTP responses. The option
does not have a default value and the feature is disbaled when a env var
is not set.
  • Loading branch information
owais committed Apr 12, 2021
1 parent 370952f commit dacd59a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#299](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/299))
- `opentelemetry-instrumenation-django` now supports request and response hooks.
([#407](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/407))
- Add trace response header support for Django.
([#395](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/395))

### Removed
- Remove `http.status_text` from span attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

from opentelemetry.context import attach, detach
from opentelemetry.instrumentation.django.version import __version__
from opentelemetry.instrumentation.propagators import (
get_global_back_propagator,
)
from opentelemetry.instrumentation.utils import extract_attributes_from_object
from opentelemetry.instrumentation.wsgi import (
add_response_attributes,
Expand Down Expand Up @@ -179,6 +182,11 @@ def process_response(self, request, response):
response,
)

propagator = get_global_back_propagator()
if propagator:
propagator.inject(response)

# record any exceptions raised while processing the request
exception = request.META.pop(self._environ_exception_key, None)
if _DjangoMiddleware._otel_response_hook:
_DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,19 @@
DjangoInstrumentor,
_DjangoMiddleware,
)
from opentelemetry.instrumentation.propagators import (
TraceResponsePropagator,
set_global_back_propagator,
)
from opentelemetry.sdk.trace import Span
from opentelemetry.test.test_base import TestBase
from opentelemetry.test.wsgitestutil import WsgiTestBase
from opentelemetry.trace import SpanKind, StatusCode
from opentelemetry.trace import (
SpanKind,
StatusCode,
format_span_id,
format_trace_id,
)
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs

# pylint: disable=import-error
Expand All @@ -41,6 +50,7 @@
route_span_name,
traced,
traced_template,
with_response_header,
)

DJANGO_2_2 = VERSION >= (2, 2)
Expand All @@ -52,6 +62,7 @@
url(r"^excluded_arg/", excluded),
url(r"^excluded_noarg/", excluded_noarg),
url(r"^excluded_noarg2/", excluded_noarg2),
url(r"^response_header/", with_response_header),
url(r"^span_name/([0-9]{4})/$", route_span_name),
]
_django_instrumentor = DjangoInstrumentor()
Expand Down Expand Up @@ -309,3 +320,55 @@ def response_hook(span, request, response):
self.assertIsInstance(response_hook_args[1], HttpRequest)
self.assertIsInstance(response_hook_args[2], HttpResponse)
self.assertEqual(response_hook_args[2], response)

def test_trace_response_headers(self):
response = Client().get("/span_name/1234/")
self.assertNotIn("Server-Timing", response._headers)
self.memory_exporter.clear()

set_global_back_propagator(TraceResponsePropagator())

response = Client().get("/span_name/1234/")
span = self.memory_exporter.get_finished_spans()[0]

self.assertIn("traceresponse", response._headers)
self.assertEqual(
response._headers["access-control-expose-headers"][0],
"Access-Control-Expose-Headers",
)
self.assertEqual(
response._headers["access-control-expose-headers"][1],
"traceresponse",
)
self.assertEqual(
response._headers["traceresponse"][0], "traceresponse"
)
self.assertEqual(
response._headers["traceresponse"][1],
"00-{0}-{1}-01".format(
format_trace_id(span.get_span_context().trace_id),
format_span_id(span.get_span_context().span_id),
),
)
self.memory_exporter.clear()

def test_trace_response_header_pre_existing_header(self):
set_global_back_propagator(TraceResponsePropagator())

response = Client().get("/response_header/")
span = self.memory_exporter.get_finished_spans()[0]
self.assertIn("traceresponse", response._headers)
self.assertEqual(
response._headers["access-control-expose-headers"][1],
"X-Test-Header, traceresponse",
)
self.assertEqual(
response._headers["traceresponse"][1],
"abc; val=1, "
+ "00-{0}-{1}-01".format(
format_trace_id(span.get_span_context().trace_id),
format_span_id(span.get_span_context().span_id),
),
)

self.memory_exporter.clear()
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from django.http import HttpResponse

from opentelemetry.instrumentation.propagators import (
_HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
)


def traced(request): # pylint: disable=unused-argument
return HttpResponse()
Expand Down Expand Up @@ -29,3 +33,10 @@ def route_span_name(
request, *args, **kwargs
): # pylint: disable=unused-argument
return HttpResponse()


def with_response_header(request): # pylint: disable=unused-argument
response = HttpResponse()
response["traceresponse"] = "abc; val=1"
response[_HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS] = "X-Test-Header"
return response

0 comments on commit dacd59a

Please sign in to comment.