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 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"},