From 64a0045743fc1f464b4bb0bc75c29755f775c828 Mon Sep 17 00:00:00 2001 From: ghdtjdwn Date: Wed, 11 Nov 2026 10:29:31 +0900 Subject: [PATCH 1/2] opentelemetry-instrumentation-wsgi: capture Content-Type and Content-Length request headers PEP 3333 passes these two headers as CONTENT_TYPE and CONTENT_LENGTH, without the HTTP_ prefix, so they were never matched by OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST. --- .../instrumentation/wsgi/__init__.py | 5 ++++ .../tests/test_wsgi_middleware.py | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py index e4c969dc9f..86b94ca7fd 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -448,6 +448,11 @@ def collect_custom_request_headers_attributes(environ: WSGIEnvironment) -> dict[ for key, val in environ.items() if key.startswith(_CARRIER_KEY_PREFIX) } + # PEP 3333: these two have no HTTP_ prefix and may be empty or absent + for key in ("CONTENT_TYPE", "CONTENT_LENGTH"): + val = environ.get(key) + if val: + headers[key.replace("_", "-")] = val return sanitize.sanitize_header_values( headers, diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py index a49a35d3de..4cbfa8a6e5 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -1208,6 +1208,31 @@ def test_custom_request_headers_added_in_server_span(self) -> None: for point in metric.data.data_points: self.assertTrue(set(expected).isdisjoint(point.attributes)) + @mock.patch.dict( + "os.environ", + {OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Content-Type,Content-Length"}, + ) + def test_custom_request_headers_content_type_and_length(self) -> None: + self.environ.update( + { + "CONTENT_TYPE": "application/json", + "CONTENT_LENGTH": "7", + "HTTP_CONTENT_TYPE": "text/html", + } + ) + self.assertEqual( + otel_wsgi.collect_custom_request_headers_attributes(self.environ), + { + "http.request.header.content_type": ["application/json"], + "http.request.header.content_length": ["7"], + }, + ) + self.environ["CONTENT_LENGTH"] = "" + self.assertEqual( + otel_wsgi.collect_custom_request_headers_attributes(self.environ), + {"http.request.header.content_type": ["application/json"]}, + ) + @mock.patch.dict( "os.environ", {OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1"}, From c5c697d31baa2476b7b8bc250eb6d83663d5d877 Mon Sep 17 00:00:00 2001 From: ghdtjdwn Date: Wed, 11 Nov 2026 10:31:09 +0900 Subject: [PATCH 2/2] Add changelog fragment --- .changelog/5321.fixed | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/5321.fixed diff --git a/.changelog/5321.fixed b/.changelog/5321.fixed new file mode 100644 index 0000000000..be4284e3b9 --- /dev/null +++ b/.changelog/5321.fixed @@ -0,0 +1 @@ +`opentelemetry-instrumentation-wsgi`: capture `Content-Type` and `Content-Length` request headers