From e85f3d63c49f44ef65627280442eb82af813963d Mon Sep 17 00:00:00 2001 From: ghdtjdwn Date: Wed, 11 Nov 2026 10:29:30 +0900 Subject: [PATCH 1/2] opentelemetry-instrumentation-urllib3: keep pool headers when urlopen gets none urllib3 falls back to the pool's default headers when urlopen() is called without headers. The instrumented urlopen replaced the missing headers with an empty dict before injecting, so the pool defaults were never sent. Start from a copy of the pool headers instead. --- .../instrumentation/urllib3/__init__.py | 2 +- .../tests/test_urllib3_integration.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py index 18a149e369b..8651791ee05 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py @@ -442,7 +442,7 @@ def instrumented_urlopen(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) # avoid modifying original headers on inject - headers = headers.copy() if headers is not None else {} + headers = headers.copy() if headers is not None else instance.headers.copy() sanitized_method = sanitize_method(method) span_name = _get_span_name(sanitized_method) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py index 4916c2ec71e..3f05bada92d 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py @@ -960,3 +960,16 @@ def test_urlopen_mixed_args(self): self.assertEqual(b"Hello!", response.data) span = self.assert_span() self.assertEqual(span.attributes["http.request.header.x_test"], ("Value",)) + + def test_urlopen_pool_headers(self): + URLLib3Instrumentor().uninstrument() + URLLib3Instrumentor().instrument(captured_request_headers=["X-Test"]) + pool = urllib3.HTTPConnectionPool("mock", headers={"X-Test": "Value"}) + response = pool.urlopen("GET", "/status/200") + self.assertEqual(b"Hello!", response.data) + span = self.assert_span() + headers = dict(Mocket.last_request().headers) + self.assertEqual(headers.get("x-test"), "Value") + self.assertIn("traceparent", headers) + self.assertEqual(span.attributes["http.request.header.x_test"], ("Value",)) + self.assertEqual(pool.headers, {"X-Test": "Value"}) From 1039ada68a7dd86243f7628639a0dfca07bd7abe Mon Sep 17 00:00:00 2001 From: ghdtjdwn Date: Wed, 11 Nov 2026 10:31:07 +0900 Subject: [PATCH 2/2] Add changelog fragment --- .changelog/5320.fixed | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/5320.fixed diff --git a/.changelog/5320.fixed b/.changelog/5320.fixed new file mode 100644 index 00000000000..9df4767330c --- /dev/null +++ b/.changelog/5320.fixed @@ -0,0 +1 @@ +`opentelemetry-instrumentation-urllib3`: keep the pool default headers when `urlopen` is called without headers