From db16b6ec8c51c7e20b0b409b6abe6593c54510b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Je=C5=BC?= Date: Thu, 6 Nov 2025 09:18:46 +0100 Subject: [PATCH 1/3] instrumentation/django: improve docs for response hooks --- .../instrumentation/django/__init__.py | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py index 4a1f3feb98..fcf97a1135 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py @@ -57,7 +57,7 @@ will extract the ``path_info`` and ``content_type`` attributes from every traced request and add them as span attributes. -Django Request object reference: https://docs.djangoproject.com/en/3.1/ref/request-response/#attributes +* `Django Request object reference `_ Request and Response hooks *************************** @@ -77,8 +77,76 @@ def response_hook(span, request, response): DjangoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) -Django Request object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httprequest-objects -Django Response object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httpresponse-objects +* `Django Request object `_ +* `Django Response object `_ + +Adding attributes from middleware context +******************************************************************************** +In many Django applications, certain request attributes become available only *after* +specific middlewares have executed. For example: + +- ``django.contrib.auth.middleware.AuthenticationMiddleware`` populates ``request.user`` +- ``django.contrib.sites.middleware.CurrentSiteMiddleware`` populates ``request.site`` + +Because the OpenTelemetry instrumentation creates the span **before** Django middlewares run, +these attributes are **not yet available** in the ``request_hook`` stage. + +Therefore, such attributes should be safely attached in the **response_hook**, which executes +after Django finishes processing the request (and after all middlewares have completed). + +Example: Attaching the authenticated user and current site to the span: + +.. code:: python + + def response_hook(span, request, response): + # Attach user information if available + if request.user.is_authenticated: + span.set_attribute("enduser.id", request.user.pk) + span.set_attribute("enduser.username", request.user.get_username()) + + # Attach current site (if provided by CurrentSiteMiddleware) + if getattr(request, "site", None): + span.set_attribute("site.id", getattr(request.site, "pk", None)) + span.set_attribute("site.domain", getattr(request.site, "domain", None)) + + DjangoInstrumentor().instrument(response_hook=response_hook) + +This ensures that middleware-dependent context (like user or site information) is properly +recorded once Django’s middleware stack has finished execution. + +Custom Django middleware can also attach arbitrary data to the ``request`` object, +which can later be included as span attributes in the ``response_hook``. + +* `Django middleware reference `_ + +Best practices +*************** +- Use **response_hook** (not request_hook) when accessing attributes added by Django middlewares. +- Common middleware-provided attributes include: + + - ``request.user`` (AuthenticationMiddleware) + - ``request.site`` (CurrentSiteMiddleware) + +- Avoid adding large or sensitive data (e.g., passwords, session tokens, PII) to spans. +- Use **namespaced attribute keys**, e.g., ``enduser.*``, ``site.*``, or ``custom.*``, for clarity. +- Hooks should execute quickly — avoid blocking or long-running operations. +- Hooks can be safely combined with OpenTelemetry **Context propagation** or **Baggage** + for consistent tracing across services. + +* `OpenTelemetry semantic conventions `_ + +Middleware execution order +****************************************** +In Django’s request lifecycle, the OpenTelemetry `request_hook` is executed before +the first middleware runs. Therefore: + +- At `request_hook` time → only the bare `HttpRequest` object is available. +- After middlewares → `request.user`, `request.site` etc. become available. +- At `response_hook` time → all middlewares (including authentication and site middlewares) + have already run, making it the correct place to attach these attributes. + +Developers who need to trace attributes from middlewares should always use `response_hook` +to ensure complete and accurate span data. Capture HTTP request and response headers ***************************************** From 4fdd0bf070e6c9cef2606f75fa26e33716c2c0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Je=C5=BC?= Date: Thu, 6 Nov 2025 14:41:06 +0100 Subject: [PATCH 2/3] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e206925782..39e870c002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3884](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3884)) - `opentelemetry-instrumentation-aiohttp-server`: add support for custom header captures via `OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST` and `OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE` ([#3916](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3916)) +- `opentelemetry-instrumentation-django`: improve docs for response_hook with examples of providing attributes from middlewares + ([#3923](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3923)) ### Fixed From 37278ebe5c0a5174fb5b620fcaf1043edbf923f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Je=C5=BC?= Date: Thu, 20 Nov 2025 11:41:49 +0100 Subject: [PATCH 3/3] modify new sections as subsections under "Request and Response hooks" --- .../opentelemetry/instrumentation/django/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py index fcf97a1135..d8bd93c053 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py @@ -45,7 +45,7 @@ will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. Request attributes -******************** +****************** To extract attributes from Django's request object and use them as span attributes, set the environment variable ``OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS`` to a comma delimited list of request attribute names. @@ -60,7 +60,7 @@ * `Django Request object reference `_ Request and Response hooks -*************************** +************************** This instrumentation supports request and response hooks. These are functions that get called right after a span is created for a request and right before the span is finished for the response. The hooks can be configured as follows: @@ -81,7 +81,7 @@ def response_hook(span, request, response): * `Django Response object `_ Adding attributes from middleware context -******************************************************************************** +######################################### In many Django applications, certain request attributes become available only *after* specific middlewares have executed. For example: @@ -105,7 +105,7 @@ def response_hook(span, request, response): span.set_attribute("enduser.username", request.user.get_username()) # Attach current site (if provided by CurrentSiteMiddleware) - if getattr(request, "site", None): + if hasattr(request, "site"): span.set_attribute("site.id", getattr(request.site, "pk", None)) span.set_attribute("site.domain", getattr(request.site, "domain", None)) @@ -120,7 +120,7 @@ def response_hook(span, request, response): * `Django middleware reference `_ Best practices -*************** +############## - Use **response_hook** (not request_hook) when accessing attributes added by Django middlewares. - Common middleware-provided attributes include: @@ -136,7 +136,7 @@ def response_hook(span, request, response): * `OpenTelemetry semantic conventions `_ Middleware execution order -****************************************** +########################## In Django’s request lifecycle, the OpenTelemetry `request_hook` is executed before the first middleware runs. Therefore: