From 369bd28fbbd2780ffc72a344eb1beed7d3169dc3 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 2 Sep 2025 11:15:10 -0700 Subject: [PATCH 01/13] fix-for-issue-2808 --- .../django/middleware/otel_middleware.py | 23 ++++++ .../tests/test_middleware.py | 79 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index f607046959..5b59b2835c 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -165,6 +165,26 @@ class _DjangoMiddleware(MiddlewareMixin): None ) + @staticmethod + def _format_request_objects_in_headers(attributes): + for key, value_list in list(attributes.items()): + new_values = [] + for value in value_list: + if hasattr(value, '__class__'): + if value.__class__.__name__ in ('HttpRequest', 'WSGIRequest'): + try: + method = getattr(value, 'method', 'UNKNOWN') + path = getattr(value, 'path', 'UNKNOWN') + new_values.append(f"HttpRequest({method} {path})") + except (AttributeError, ValueError, TypeError): + new_values.append("HttpRequest(...)") + else: + new_values.append(value) + else: + new_values.append(value) + attributes[key] = new_values + return attributes + @staticmethod def _get_span_name(request): method = sanitize_method(request.method.strip()) @@ -276,6 +296,9 @@ def process_request(self, request): custom_attributes = ( wsgi_collect_custom_request_headers_attributes(carrier) ) + # Process custom attributes to handle WSGIRequest objects + custom_attributes = self._format_request_objects_in_headers(custom_attributes) + if len(custom_attributes) > 0: span.set_attributes(custom_attributes) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 960bf97bc4..76a328454d 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -23,6 +23,7 @@ from django.http import HttpRequest, HttpResponse from django.test.client import Client from django.test.utils import setup_test_environment, teardown_test_environment +from django.core.handlers.wsgi import WSGIRequest from opentelemetry import trace from opentelemetry.instrumentation._semconv import ( @@ -1019,6 +1020,84 @@ def tearDownClass(cls): super().tearDownClass() conf.settings = conf.LazySettings() + @staticmethod + def _format_request_objects_in_headers(attributes): + for key, value_list in list(attributes.items()): + new_values = [] + for value in value_list: + if hasattr(value, '__class__'): + if value.__class__.__name__ in ('HttpRequest', 'WSGIRequest'): + try: + method = getattr(value, 'method', 'UNKNOWN') + path = getattr(value, 'path', 'UNKNOWN') + new_values.append(f"HttpRequest({method} {path})") + except (AttributeError, ValueError, TypeError): + new_values.append("HttpRequest(...)") + else: + new_values.append(value) + else: + new_values.append(value) + attributes[key] = new_values + return attributes + + def test_wsgi_request_in_header_is_properly_formatted(self): + mock_wsgi_request = Mock(spec=WSGIRequest) + mock_wsgi_request.method = "GET" + mock_wsgi_request.path = "/test/path" + mock_wsgi_request.__class__.__name__ = "WSGIRequest" + + input_attributes = {"http.request.header.test_wsgirequest_header": [mock_wsgi_request]} + expected_attributes = {"http.request.header.test_wsgirequest_header": ["HttpRequest(GET /test/path)"]} + + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + + self.assertEqual(formatted_attributes, expected_attributes) + + def test_wsgi_request_handles_extraction_error(self): + mock_wsgi_request = Mock(spec=WSGIRequest) + mock_wsgi_request.__class__.__name__ = "WSGIRequest" + + type(mock_wsgi_request).method = property(lambda self: (_ for _ in ()).throw(ValueError("Test error"))) + + input_attributes = {"http.request.header.test_wsgirequest_header": [mock_wsgi_request]} + expected_attributes = {"http.request.header.test_wsgirequest_header": ["HttpRequest(...)"]} + + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + + self.assertEqual(formatted_attributes, expected_attributes) + + def test_handles_http_request_as_well(self): + mock_http_request = Mock(spec=HttpRequest) + mock_http_request.method = "POST" + mock_http_request.path = "/api/data" + mock_http_request.__class__.__name__ = "HttpRequest" + + input_attributes = {"http.request.header.test_httprequest_header": [mock_http_request]} + expected_attributes = {"http.request.header.test_httprequest_header": ["HttpRequest(POST /api/data)"]} + + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + + self.assertEqual(formatted_attributes, expected_attributes) + + def test_regular_header_values_are_preserved(self): + mock_wsgi_request = Mock(spec=WSGIRequest) + mock_wsgi_request.method = "GET" + mock_wsgi_request.path = "/test/path" + mock_wsgi_request.__class__.__name__ = "WSGIRequest" + + input_attributes = { + "http.request.header.test_wsgirequest_header": [mock_wsgi_request], + "http.request.header.test_regular_header": ["regular-value"] + } + expected_attributes = { + "http.request.header.test_wsgirequest_header": ["HttpRequest(GET /test/path)"], + "http.request.header.test_regular_header": ["regular-value"] + } + + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + + self.assertEqual(formatted_attributes, expected_attributes) + def test_http_custom_request_headers_in_span_attributes(self): expected = { "http.request.header.custom_test_header_1": ( From bd6c8fc1135e5f8557685bde43fd74e6a4d2bcfe Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 2 Sep 2025 14:33:37 -0700 Subject: [PATCH 02/13] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0f788197..d4736c75fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3673](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3673)) - `opentelemetry-instrumentation-starlette`/`opentelemetry-instrumentation-fastapi`: Fixes a crash when host-based routing is used ([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507)) +- `opentelemetry-instrumentation-fastapi`: Fixes issue [#2808](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2808) ### Added From aae405b0106c7024fbbc7878d1b14fbb5b87b87d Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 2 Sep 2025 16:12:58 -0700 Subject: [PATCH 03/13] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4736c75fd..0ee2ca61fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-starlette`/`opentelemetry-instrumentation-fastapi`: Fixes a crash when host-based routing is used ([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507)) - `opentelemetry-instrumentation-fastapi`: Fixes issue [#2808](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2808) + ([#3731](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3731)) ### Added From 1c1f4199558c322c1d13ae01cd235909863d67a2 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 2 Sep 2025 16:45:21 -0700 Subject: [PATCH 04/13] Fixed lint --- .../django/middleware/otel_middleware.py | 17 +++-- .../tests/test_middleware.py | 73 ++++++++++++++----- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 5b59b2835c..33ca111168 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -170,11 +170,14 @@ def _format_request_objects_in_headers(attributes): for key, value_list in list(attributes.items()): new_values = [] for value in value_list: - if hasattr(value, '__class__'): - if value.__class__.__name__ in ('HttpRequest', 'WSGIRequest'): + if hasattr(value, "__class__"): + if value.__class__.__name__ in ( + "HttpRequest", + "WSGIRequest", + ): try: - method = getattr(value, 'method', 'UNKNOWN') - path = getattr(value, 'path', 'UNKNOWN') + method = getattr(value, "method", "UNKNOWN") + path = getattr(value, "path", "UNKNOWN") new_values.append(f"HttpRequest({method} {path})") except (AttributeError, ValueError, TypeError): new_values.append("HttpRequest(...)") @@ -297,7 +300,11 @@ def process_request(self, request): wsgi_collect_custom_request_headers_attributes(carrier) ) # Process custom attributes to handle WSGIRequest objects - custom_attributes = self._format_request_objects_in_headers(custom_attributes) + custom_attributes = ( + self._format_request_objects_in_headers( + custom_attributes + ) + ) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 76a328454d..2e8741acf7 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -20,10 +20,10 @@ from unittest.mock import Mock, patch from django import VERSION, conf +from django.core.handlers.wsgi import WSGIRequest from django.http import HttpRequest, HttpResponse from django.test.client import Client from django.test.utils import setup_test_environment, teardown_test_environment -from django.core.handlers.wsgi import WSGIRequest from opentelemetry import trace from opentelemetry.instrumentation._semconv import ( @@ -1025,12 +1025,17 @@ def _format_request_objects_in_headers(attributes): for key, value_list in list(attributes.items()): new_values = [] for value in value_list: - if hasattr(value, '__class__'): - if value.__class__.__name__ in ('HttpRequest', 'WSGIRequest'): + if hasattr(value, "__class__"): + if value.__class__.__name__ in ( + "HttpRequest", + "WSGIRequest", + ): try: - method = getattr(value, 'method', 'UNKNOWN') - path = getattr(value, 'path', 'UNKNOWN') - new_values.append(f"HttpRequest({method} {path})") + method = getattr(value, "method", "UNKNOWN") + request_path = getattr(value, "path", "UNKNOWN") + new_values.append( + f"HttpRequest({method} {request_path})" + ) except (AttributeError, ValueError, TypeError): new_values.append("HttpRequest(...)") else: @@ -1046,10 +1051,18 @@ def test_wsgi_request_in_header_is_properly_formatted(self): mock_wsgi_request.path = "/test/path" mock_wsgi_request.__class__.__name__ = "WSGIRequest" - input_attributes = {"http.request.header.test_wsgirequest_header": [mock_wsgi_request]} - expected_attributes = {"http.request.header.test_wsgirequest_header": ["HttpRequest(GET /test/path)"]} + input_attributes = { + "http.request.header.test_wsgirequest_header": [mock_wsgi_request] + } + expected_attributes = { + "http.request.header.test_wsgirequest_header": [ + "HttpRequest(GET /test/path)" + ] + } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + input_attributes + ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1057,12 +1070,20 @@ def test_wsgi_request_handles_extraction_error(self): mock_wsgi_request = Mock(spec=WSGIRequest) mock_wsgi_request.__class__.__name__ = "WSGIRequest" - type(mock_wsgi_request).method = property(lambda self: (_ for _ in ()).throw(ValueError("Test error"))) + type(mock_wsgi_request).method = property( + lambda self: (_ for _ in ()).throw(ValueError("Test error")) + ) - input_attributes = {"http.request.header.test_wsgirequest_header": [mock_wsgi_request]} - expected_attributes = {"http.request.header.test_wsgirequest_header": ["HttpRequest(...)"]} + input_attributes = { + "http.request.header.test_wsgirequest_header": [mock_wsgi_request] + } + expected_attributes = { + "http.request.header.test_wsgirequest_header": ["HttpRequest(...)"] + } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + input_attributes + ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1072,10 +1093,18 @@ def test_handles_http_request_as_well(self): mock_http_request.path = "/api/data" mock_http_request.__class__.__name__ = "HttpRequest" - input_attributes = {"http.request.header.test_httprequest_header": [mock_http_request]} - expected_attributes = {"http.request.header.test_httprequest_header": ["HttpRequest(POST /api/data)"]} + input_attributes = { + "http.request.header.test_httprequest_header": [mock_http_request] + } + expected_attributes = { + "http.request.header.test_httprequest_header": [ + "HttpRequest(POST /api/data)" + ] + } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + input_attributes + ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1087,14 +1116,18 @@ def test_regular_header_values_are_preserved(self): input_attributes = { "http.request.header.test_wsgirequest_header": [mock_wsgi_request], - "http.request.header.test_regular_header": ["regular-value"] + "http.request.header.test_regular_header": ["regular-value"], } expected_attributes = { - "http.request.header.test_wsgirequest_header": ["HttpRequest(GET /test/path)"], - "http.request.header.test_regular_header": ["regular-value"] + "http.request.header.test_wsgirequest_header": [ + "HttpRequest(GET /test/path)" + ], + "http.request.header.test_regular_header": ["regular-value"], } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers(input_attributes) + formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + input_attributes + ) self.assertEqual(formatted_attributes, expected_attributes) From 52607a2477884f4f431347953d3911870d4e3633 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 3 Sep 2025 12:37:26 -0700 Subject: [PATCH 05/13] Addressed feedback --- CHANGELOG.md | 2 +- .../django/middleware/otel_middleware.py | 14 ++++---- .../tests/test_middleware.py | 33 +++---------------- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0683604d..57987c44d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3673](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3673)) - `opentelemetry-instrumentation-starlette`/`opentelemetry-instrumentation-fastapi`: Fixes a crash when host-based routing is used ([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507)) -- `opentelemetry-instrumentation-fastapi`: Fixes issue [#2808](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2808) +- `opentelemetry-instrumentation-django`: Fixes invalid type at WSGI request headers and attributes collection. ([#3731](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3731)) - Fix documentation order of sections and headers for Django, Flask, MySQL, mysqlclient, psycopg, psycopg2, pymysql, sqlalchemy instrumentations. ([#3719](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3719)) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 33ca111168..2a81f066fe 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -20,6 +20,7 @@ from django import VERSION as django_version from django.http import HttpRequest, HttpResponse +from django.core.handlers.wsgi import WSGIRequest from opentelemetry.context import detach from opentelemetry.instrumentation._semconv import ( @@ -166,19 +167,16 @@ class _DjangoMiddleware(MiddlewareMixin): ) @staticmethod - def _format_request_objects_in_headers(attributes): + def format_request_objects_in_headers(attributes): for key, value_list in list(attributes.items()): new_values = [] for value in value_list: if hasattr(value, "__class__"): - if value.__class__.__name__ in ( - "HttpRequest", - "WSGIRequest", - ): + if isinstance(value, (HttpRequest, WSGIRequest)): try: method = getattr(value, "method", "UNKNOWN") - path = getattr(value, "path", "UNKNOWN") - new_values.append(f"HttpRequest({method} {path})") + request_path = getattr(value, "path", "UNKNOWN") + new_values.append(f"HttpRequest({method} {request_path})") except (AttributeError, ValueError, TypeError): new_values.append("HttpRequest(...)") else: @@ -301,7 +299,7 @@ def process_request(self, request): ) # Process custom attributes to handle WSGIRequest objects custom_attributes = ( - self._format_request_objects_in_headers( + self.format_request_objects_in_headers( custom_attributes ) ) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 2e8741acf7..308423cd05 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -1020,31 +1020,6 @@ def tearDownClass(cls): super().tearDownClass() conf.settings = conf.LazySettings() - @staticmethod - def _format_request_objects_in_headers(attributes): - for key, value_list in list(attributes.items()): - new_values = [] - for value in value_list: - if hasattr(value, "__class__"): - if value.__class__.__name__ in ( - "HttpRequest", - "WSGIRequest", - ): - try: - method = getattr(value, "method", "UNKNOWN") - request_path = getattr(value, "path", "UNKNOWN") - new_values.append( - f"HttpRequest({method} {request_path})" - ) - except (AttributeError, ValueError, TypeError): - new_values.append("HttpRequest(...)") - else: - new_values.append(value) - else: - new_values.append(value) - attributes[key] = new_values - return attributes - def test_wsgi_request_in_header_is_properly_formatted(self): mock_wsgi_request = Mock(spec=WSGIRequest) mock_wsgi_request.method = "GET" @@ -1060,7 +1035,7 @@ def test_wsgi_request_in_header_is_properly_formatted(self): ] } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + formatted_attributes =_DjangoMiddleware.format_request_objects_in_headers( input_attributes ) @@ -1081,7 +1056,7 @@ def test_wsgi_request_handles_extraction_error(self): "http.request.header.test_wsgirequest_header": ["HttpRequest(...)"] } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( input_attributes ) @@ -1102,7 +1077,7 @@ def test_handles_http_request_as_well(self): ] } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( input_attributes ) @@ -1125,7 +1100,7 @@ def test_regular_header_values_are_preserved(self): "http.request.header.test_regular_header": ["regular-value"], } - formatted_attributes = TestMiddlewareWsgiWithCustomHeaders._format_request_objects_in_headers( + formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( input_attributes ) From 73bf9b56146a923d1595edfad43ecc4b5faca23f Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 3 Sep 2025 12:47:03 -0700 Subject: [PATCH 06/13] Fix ruff --- .../django/middleware/otel_middleware.py | 12 +++++----- .../tests/test_middleware.py | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 2a81f066fe..f43301aa5b 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -19,8 +19,8 @@ from typing import Callable from django import VERSION as django_version -from django.http import HttpRequest, HttpResponse from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpRequest, HttpResponse from opentelemetry.context import detach from opentelemetry.instrumentation._semconv import ( @@ -176,7 +176,9 @@ def format_request_objects_in_headers(attributes): try: method = getattr(value, "method", "UNKNOWN") request_path = getattr(value, "path", "UNKNOWN") - new_values.append(f"HttpRequest({method} {request_path})") + new_values.append( + f"HttpRequest({method} {request_path})" + ) except (AttributeError, ValueError, TypeError): new_values.append("HttpRequest(...)") else: @@ -298,10 +300,8 @@ def process_request(self, request): wsgi_collect_custom_request_headers_attributes(carrier) ) # Process custom attributes to handle WSGIRequest objects - custom_attributes = ( - self.format_request_objects_in_headers( - custom_attributes - ) + custom_attributes = self.format_request_objects_in_headers( + custom_attributes ) if len(custom_attributes) > 0: diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 308423cd05..a5c9dcea1d 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -1035,8 +1035,10 @@ def test_wsgi_request_in_header_is_properly_formatted(self): ] } - formatted_attributes =_DjangoMiddleware.format_request_objects_in_headers( - input_attributes + formatted_attributes = ( + _DjangoMiddleware.format_request_objects_in_headers( + input_attributes + ) ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1056,8 +1058,10 @@ def test_wsgi_request_handles_extraction_error(self): "http.request.header.test_wsgirequest_header": ["HttpRequest(...)"] } - formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( - input_attributes + formatted_attributes = ( + _DjangoMiddleware.format_request_objects_in_headers( + input_attributes + ) ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1077,8 +1081,10 @@ def test_handles_http_request_as_well(self): ] } - formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( - input_attributes + formatted_attributes = ( + _DjangoMiddleware.format_request_objects_in_headers( + input_attributes + ) ) self.assertEqual(formatted_attributes, expected_attributes) @@ -1100,8 +1106,10 @@ def test_regular_header_values_are_preserved(self): "http.request.header.test_regular_header": ["regular-value"], } - formatted_attributes = _DjangoMiddleware.format_request_objects_in_headers( - input_attributes + formatted_attributes = ( + _DjangoMiddleware.format_request_objects_in_headers( + input_attributes + ) ) self.assertEqual(formatted_attributes, expected_attributes) From afb09238d3d627d10010b8f0afd1d196f34ff553 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 3 Sep 2025 14:09:17 -0700 Subject: [PATCH 07/13] Fix format --- .../instrumentation/django/middleware/otel_middleware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index f43301aa5b..fd2194c4a2 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -19,7 +19,6 @@ from typing import Callable from django import VERSION as django_version -from django.core.handlers.wsgi import WSGIRequest from django.http import HttpRequest, HttpResponse from opentelemetry.context import detach @@ -172,7 +171,7 @@ def format_request_objects_in_headers(attributes): new_values = [] for value in value_list: if hasattr(value, "__class__"): - if isinstance(value, (HttpRequest, WSGIRequest)): + if isinstance(value, HttpRequest): try: method = getattr(value, "method", "UNKNOWN") request_path = getattr(value, "path", "UNKNOWN") From 27498de89731064790ed4ffaaf39a4ccdc4c5771 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 10 Sep 2025 12:26:22 -0700 Subject: [PATCH 08/13] Addressed feedback --- .../django/middleware/otel_middleware.py | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index fd2194c4a2..4b7c5c0e96 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -167,24 +167,15 @@ class _DjangoMiddleware(MiddlewareMixin): @staticmethod def format_request_objects_in_headers(attributes): - for key, value_list in list(attributes.items()): - new_values = [] - for value in value_list: - if hasattr(value, "__class__"): - if isinstance(value, HttpRequest): - try: - method = getattr(value, "method", "UNKNOWN") - request_path = getattr(value, "path", "UNKNOWN") - new_values.append( - f"HttpRequest({method} {request_path})" - ) - except (AttributeError, ValueError, TypeError): - new_values.append("HttpRequest(...)") - else: - new_values.append(value) - else: - new_values.append(value) - attributes[key] = new_values + for key, value_list in attributes.items(): + for i, value in enumerate(value_list): + if isinstance(value, HttpRequest): + try: + method = getattr(value, "method", "UNKNOWN") + request_path = getattr(value, "path", "UNKNOWN") + value_list[i] = f"HttpRequest({method} {request_path})" + except Exception: + value_list[i] = "HttpRequest(...)" return attributes @staticmethod From 4a117bf1ae5bada3c40e270e99699184701c3fc2 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 10 Sep 2025 12:44:19 -0700 Subject: [PATCH 09/13] Fix lint --- .../django/middleware/otel_middleware.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 4b7c5c0e96..b8a314850d 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -167,15 +167,15 @@ class _DjangoMiddleware(MiddlewareMixin): @staticmethod def format_request_objects_in_headers(attributes): - for key, value_list in attributes.items(): - for i, value in enumerate(value_list): + for _, value_list in attributes.items(): + for index, value in enumerate(value_list): if isinstance(value, HttpRequest): try: method = getattr(value, "method", "UNKNOWN") request_path = getattr(value, "path", "UNKNOWN") - value_list[i] = f"HttpRequest({method} {request_path})" - except Exception: - value_list[i] = "HttpRequest(...)" + value_list[index] = f"HttpRequest({method} {request_path})" + except Exception: # pylint: disable=broad-exception-caught + value_list[index] = "HttpRequest(...)" return attributes @staticmethod From f38030733da0bb87755b8074b7f5f890e10b1f37 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 10 Sep 2025 12:54:13 -0700 Subject: [PATCH 10/13] Fix ruff --- .../instrumentation/django/middleware/otel_middleware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index b8a314850d..1b7d09ee45 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -173,8 +173,10 @@ def format_request_objects_in_headers(attributes): try: method = getattr(value, "method", "UNKNOWN") request_path = getattr(value, "path", "UNKNOWN") - value_list[index] = f"HttpRequest({method} {request_path})" - except Exception: # pylint: disable=broad-exception-caught + value_list[index] = ( + f"HttpRequest({method} {request_path})" + ) + except Exception: # pylint: disable=broad-exception-caught value_list[index] = "HttpRequest(...)" return attributes From d7f1ce0b1f54cb94d2c32522775380b83c0f16ea Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 13 Oct 2025 15:18:15 -0700 Subject: [PATCH 11/13] Addressed comments --- CHANGELOG.md | 4 ++-- .../instrumentation/django/middleware/otel_middleware.py | 9 +-------- .../tests/test_middleware.py | 8 ++++---- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abdf12ce7..6700459df3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `opentelemetry-instrumentation-django`: Fixes invalid type at WSGI request headers and attributes collection. + ([#3731](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3731)) - `opentelemetry-instrumentation-botocore`: migrate off the deprecated events API to use the logs API ([#3624](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3624)) - `opentelemetry-instrumentation-dbapi`: fix crash retrieving libpq version when enabling commenter with psycopg @@ -51,8 +53,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3673](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3673)) - `opentelemetry-instrumentation-starlette`/`opentelemetry-instrumentation-fastapi`: Fixes a crash when host-based routing is used ([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507)) -- `opentelemetry-instrumentation-django`: Fixes invalid type at WSGI request headers and attributes collection. - ([#3731](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3731)) - Fix documentation order of sections and headers for Django, Flask, MySQL, mysqlclient, psycopg, psycopg2, pymysql, sqlalchemy instrumentations. ([#3719](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3719)) - `opentelemetry-instrumentation-asgi` Fixed an issue where FastAPI reports IP instead of URL. diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 1b7d09ee45..27c768b348 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -170,14 +170,7 @@ def format_request_objects_in_headers(attributes): for _, value_list in attributes.items(): for index, value in enumerate(value_list): if isinstance(value, HttpRequest): - try: - method = getattr(value, "method", "UNKNOWN") - request_path = getattr(value, "path", "UNKNOWN") - value_list[index] = ( - f"HttpRequest({method} {request_path})" - ) - except Exception: # pylint: disable=broad-exception-caught - value_list[index] = "HttpRequest(...)" + value_list[index] = str(value) return attributes @staticmethod diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index a5c9dcea1d..5fb31046af 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -1031,7 +1031,7 @@ def test_wsgi_request_in_header_is_properly_formatted(self): } expected_attributes = { "http.request.header.test_wsgirequest_header": [ - "HttpRequest(GET /test/path)" + str(mock_wsgi_request) ] } @@ -1055,7 +1055,7 @@ def test_wsgi_request_handles_extraction_error(self): "http.request.header.test_wsgirequest_header": [mock_wsgi_request] } expected_attributes = { - "http.request.header.test_wsgirequest_header": ["HttpRequest(...)"] + "http.request.header.test_wsgirequest_header": str(mock_wsgi_request) } formatted_attributes = ( @@ -1077,7 +1077,7 @@ def test_handles_http_request_as_well(self): } expected_attributes = { "http.request.header.test_httprequest_header": [ - "HttpRequest(POST /api/data)" + str(mock_http_request) ] } @@ -1101,7 +1101,7 @@ def test_regular_header_values_are_preserved(self): } expected_attributes = { "http.request.header.test_wsgirequest_header": [ - "HttpRequest(GET /test/path)" + str(mock_wsgi_request) ], "http.request.header.test_regular_header": ["regular-value"], } From 08e817ceb2dda2118634938dbdbfc8079b1e3848 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 13 Oct 2025 15:43:20 -0700 Subject: [PATCH 12/13] Fix ruff --- .../tests/test_middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 5fb31046af..3230b8d689 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -1055,7 +1055,9 @@ def test_wsgi_request_handles_extraction_error(self): "http.request.header.test_wsgirequest_header": [mock_wsgi_request] } expected_attributes = { - "http.request.header.test_wsgirequest_header": str(mock_wsgi_request) + "http.request.header.test_wsgirequest_header": str( + mock_wsgi_request + ) } formatted_attributes = ( From c79c917d47b63df6e898831cb400f473e7b337e5 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 13 Oct 2025 16:09:54 -0700 Subject: [PATCH 13/13] Fix test --- .../tests/test_middleware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 3230b8d689..8f757adc54 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -1055,9 +1055,9 @@ def test_wsgi_request_handles_extraction_error(self): "http.request.header.test_wsgirequest_header": [mock_wsgi_request] } expected_attributes = { - "http.request.header.test_wsgirequest_header": str( - mock_wsgi_request - ) + "http.request.header.test_wsgirequest_header": [ + str(mock_wsgi_request) + ] } formatted_attributes = (