From 512d0c32ee00daca727573ed8203cf4dc045a58e Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Tue, 11 Nov 2025 21:50:09 -0500 Subject: [PATCH 1/3] opentelemetry-instrumentation-aiohttp-client: fix metric attribute leakage --- .../aiohttp_client/__init__.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py index d6869bcd36..e9cf56153e 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py @@ -287,8 +287,6 @@ def create_trace_config( explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW, ) - metric_attributes = {} - excluded_urls = get_excluded_urls("AIOHTTP_CLIENT") def _end_trace(trace_config_ctx: types.SimpleNamespace): @@ -299,7 +297,7 @@ def _end_trace(trace_config_ctx: types.SimpleNamespace): if trace_config_ctx.duration_histogram_old is not None: duration_attrs_old = _filter_semconv_duration_attrs( - metric_attributes, + trace_config_ctx.metric_attributes, _client_duration_attrs_old, _client_duration_attrs_new, _StabilityMode.DEFAULT, @@ -310,7 +308,7 @@ def _end_trace(trace_config_ctx: types.SimpleNamespace): ) if trace_config_ctx.duration_histogram_new is not None: duration_attrs_new = _filter_semconv_duration_attrs( - metric_attributes, + trace_config_ctx.metric_attributes, _client_duration_attrs_old, _client_duration_attrs_new, _StabilityMode.HTTP, @@ -348,7 +346,7 @@ async def on_request_start( sem_conv_opt_in_mode, ) _set_http_method( - metric_attributes, + trace_config_ctx.metric_attributes, method, sanitize_method(method), sem_conv_opt_in_mode, @@ -359,12 +357,12 @@ async def on_request_start( parsed_url = urlparse(request_url) if parsed_url.hostname: _set_http_host_client( - metric_attributes, + trace_config_ctx.metric_attributes, parsed_url.hostname, sem_conv_opt_in_mode, ) _set_http_net_peer_name_client( - metric_attributes, + trace_config_ctx.metric_attributes, parsed_url.hostname, sem_conv_opt_in_mode, ) @@ -376,7 +374,9 @@ async def on_request_start( ) if parsed_url.port: _set_http_peer_port_client( - metric_attributes, parsed_url.port, sem_conv_opt_in_mode + trace_config_ctx.metric_attributes, + parsed_url.port, + sem_conv_opt_in_mode, ) if _report_new(sem_conv_opt_in_mode): _set_http_peer_port_client( @@ -411,7 +411,7 @@ async def on_request_end( _set_http_status_code_attribute( trace_config_ctx.span, params.response.status, - metric_attributes, + trace_config_ctx.metric_attributes, sem_conv_opt_in_mode, ) @@ -429,7 +429,7 @@ async def on_request_exception( exc_type = type(params.exception).__qualname__ if _report_new(sem_conv_opt_in_mode): trace_config_ctx.span.set_attribute(ERROR_TYPE, exc_type) - metric_attributes[ERROR_TYPE] = exc_type + trace_config_ctx.metric_attributes[ERROR_TYPE] = exc_type trace_config_ctx.span.set_status( Status(StatusCode.ERROR, exc_type) @@ -450,6 +450,7 @@ def _trace_config_ctx_factory(**kwargs): duration_histogram_old=duration_histogram_old, duration_histogram_new=duration_histogram_new, excluded_urls=excluded_urls, + metric_attributes={}, **kwargs, ) From f9684c8189b4c541eb7a663f82ed70974a9d348d Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Wed, 12 Nov 2025 14:53:50 -0500 Subject: [PATCH 2/3] opentelemetry-instrumentation-aiohttp-client: add tests verifying isolation of metric attributes --- .../tests/test_aiohttp_client_integration.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py index 87d75afc70..1e733489c3 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py @@ -828,6 +828,52 @@ async def request_handler(request): self._assert_spans([], 0) self._assert_metrics(0) + def test_metric_attributes_isolation(self): + async def success_handler(request): + assert "traceparent" in request.headers + return aiohttp.web.Response(status=HTTPStatus.OK) + + async def timeout_handler(request): + await asyncio.sleep(60) + assert "traceparent" in request.headers + return aiohttp.web.Response(status=HTTPStatus.OK) + + success_host, success_port = self._http_request( + trace_config=aiohttp_client.create_trace_config(), + url="/success", + request_handler=success_handler, + ) + + timeout_host, timeout_port = self._http_request( + trace_config=aiohttp_client.create_trace_config(), + url="/timeout", + request_handler=timeout_handler, + timeout=aiohttp.ClientTimeout(sock_read=0.01), + ) + + metrics = self._assert_metrics(1) + duration_dp_attributes = [ + dict(dp.attributes) for dp in metrics[0].data.data_points + ] + self.assertEqual( + [ + { + HTTP_METHOD: "GET", + HTTP_HOST: success_host, + HTTP_STATUS_CODE: int(HTTPStatus.OK), + NET_PEER_NAME: success_host, + NET_PEER_PORT: success_port, + }, + { + HTTP_METHOD: "GET", + HTTP_HOST: timeout_host, + NET_PEER_NAME: timeout_host, + NET_PEER_PORT: timeout_port, + }, + ], + duration_dp_attributes, + ) + class TestAioHttpClientInstrumentor(TestBase): URL = "/test-path" From a30ab03be3588ebefada954f364cb4d1c57adb55 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Mon, 17 Nov 2025 13:46:11 -0500 Subject: [PATCH 3/3] update tests and CHANGELOG.md --- CHANGELOG.md | 2 ++ .../tests/test_aiohttp_client_integration.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a167eb85..5430a61586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3904](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3904)) - build: bump ruff to 0.14.1 ([#3842](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3842)) +- `opentelemetry-instrumentation-aiohttp-client`: Fix metric attribute leakage + ([#3936](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3936)) ## Version 1.38.0/0.59b0 (2025-10-16) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py index 1e733489c3..a15356c7ea 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py @@ -838,14 +838,18 @@ async def timeout_handler(request): assert "traceparent" in request.headers return aiohttp.web.Response(status=HTTPStatus.OK) + trace_config: aiohttp.TraceConfig = ( + aiohttp_client.create_trace_config() + ) + success_host, success_port = self._http_request( - trace_config=aiohttp_client.create_trace_config(), + trace_config=trace_config, url="/success", request_handler=success_handler, ) timeout_host, timeout_port = self._http_request( - trace_config=aiohttp_client.create_trace_config(), + trace_config=trace_config, url="/timeout", request_handler=timeout_handler, timeout=aiohttp.ClientTimeout(sock_read=0.01),