Skip to content

Commit ea1c3f4

Browse files
authored
Merge branch 'main' into feature/system-metrics-env-var-conf
2 parents 90ba86a + ccea42c commit ea1c3f4

File tree

5 files changed

+296
-34
lines changed

5 files changed

+296
-34
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
([#3884](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3884))
2828
- `opentelemetry-instrumentation-aiohttp-server`: add support for custom header captures via `OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST` and `OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`
2929
([#3916](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3916))
30+
- `opentelemetry-instrumentation-redis`: add support for `suppress_instrumentation` context manager for both sync and async Redis clients and pipelines
3031

3132
### Fixed
3233

@@ -54,6 +55,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5455
([#3941](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3941))
5556
- `opentelemetry-instrumentation-pymongo`: Fix invalid mongodb collection attribute type
5657
([#3942](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3942))
58+
- `opentelemetry-instrumentation-aiohttp-client`: Fix metric attribute leakage
59+
([#3936](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3936))
60+
- `opentelemetry-instrumentation-aiohttp-client`: Update instrumentor to respect suppressing http instrumentation
61+
([#3957](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3957))
5762
- `opentelemetry-instrumentation-system-metrics`: Add support for the `OTEL_PYTHON_SYSTEM_METRICS_EXCLUDED_METRICS` environment variable
5863
([#3959](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3959))
5964

instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def response_hook(span: Span, params: typing.Union[
137137
from opentelemetry.instrumentation.aiohttp_client.version import __version__
138138
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
139139
from opentelemetry.instrumentation.utils import (
140-
is_instrumentation_enabled,
140+
is_http_instrumentation_enabled,
141141
unwrap,
142142
)
143143
from opentelemetry.metrics import MeterProvider, get_meter
@@ -287,8 +287,6 @@ def create_trace_config(
287287
explicit_bucket_boundaries_advisory=HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
288288
)
289289

290-
metric_attributes = {}
291-
292290
excluded_urls = get_excluded_urls("AIOHTTP_CLIENT")
293291

294292
def _end_trace(trace_config_ctx: types.SimpleNamespace):
@@ -299,7 +297,7 @@ def _end_trace(trace_config_ctx: types.SimpleNamespace):
299297

300298
if trace_config_ctx.duration_histogram_old is not None:
301299
duration_attrs_old = _filter_semconv_duration_attrs(
302-
metric_attributes,
300+
trace_config_ctx.metric_attributes,
303301
_client_duration_attrs_old,
304302
_client_duration_attrs_new,
305303
_StabilityMode.DEFAULT,
@@ -310,7 +308,7 @@ def _end_trace(trace_config_ctx: types.SimpleNamespace):
310308
)
311309
if trace_config_ctx.duration_histogram_new is not None:
312310
duration_attrs_new = _filter_semconv_duration_attrs(
313-
metric_attributes,
311+
trace_config_ctx.metric_attributes,
314312
_client_duration_attrs_old,
315313
_client_duration_attrs_new,
316314
_StabilityMode.HTTP,
@@ -325,7 +323,7 @@ async def on_request_start(
325323
params: aiohttp.TraceRequestStartParams,
326324
):
327325
if (
328-
not is_instrumentation_enabled()
326+
not is_http_instrumentation_enabled()
329327
or trace_config_ctx.excluded_urls.url_disabled(str(params.url))
330328
):
331329
trace_config_ctx.span = None
@@ -348,7 +346,7 @@ async def on_request_start(
348346
sem_conv_opt_in_mode,
349347
)
350348
_set_http_method(
351-
metric_attributes,
349+
trace_config_ctx.metric_attributes,
352350
method,
353351
sanitize_method(method),
354352
sem_conv_opt_in_mode,
@@ -359,12 +357,12 @@ async def on_request_start(
359357
parsed_url = urlparse(request_url)
360358
if parsed_url.hostname:
361359
_set_http_host_client(
362-
metric_attributes,
360+
trace_config_ctx.metric_attributes,
363361
parsed_url.hostname,
364362
sem_conv_opt_in_mode,
365363
)
366364
_set_http_net_peer_name_client(
367-
metric_attributes,
365+
trace_config_ctx.metric_attributes,
368366
parsed_url.hostname,
369367
sem_conv_opt_in_mode,
370368
)
@@ -376,7 +374,9 @@ async def on_request_start(
376374
)
377375
if parsed_url.port:
378376
_set_http_peer_port_client(
379-
metric_attributes, parsed_url.port, sem_conv_opt_in_mode
377+
trace_config_ctx.metric_attributes,
378+
parsed_url.port,
379+
sem_conv_opt_in_mode,
380380
)
381381
if _report_new(sem_conv_opt_in_mode):
382382
_set_http_peer_port_client(
@@ -411,7 +411,7 @@ async def on_request_end(
411411
_set_http_status_code_attribute(
412412
trace_config_ctx.span,
413413
params.response.status,
414-
metric_attributes,
414+
trace_config_ctx.metric_attributes,
415415
sem_conv_opt_in_mode,
416416
)
417417

@@ -429,7 +429,7 @@ async def on_request_exception(
429429
exc_type = type(params.exception).__qualname__
430430
if _report_new(sem_conv_opt_in_mode):
431431
trace_config_ctx.span.set_attribute(ERROR_TYPE, exc_type)
432-
metric_attributes[ERROR_TYPE] = exc_type
432+
trace_config_ctx.metric_attributes[ERROR_TYPE] = exc_type
433433

434434
trace_config_ctx.span.set_status(
435435
Status(StatusCode.ERROR, exc_type)
@@ -450,6 +450,7 @@ def _trace_config_ctx_factory(**kwargs):
450450
duration_histogram_old=duration_histogram_old,
451451
duration_histogram_new=duration_histogram_new,
452452
excluded_urls=excluded_urls,
453+
metric_attributes={},
453454
**kwargs,
454455
)
455456

@@ -485,9 +486,6 @@ def _instrument(
485486

486487
# pylint:disable=unused-argument
487488
def instrumented_init(wrapped, instance, args, kwargs):
488-
if not is_instrumentation_enabled():
489-
return wrapped(*args, **kwargs)
490-
491489
client_trace_configs = list(kwargs.get("trace_configs") or [])
492490
client_trace_configs.extend(trace_configs)
493491

instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py

Lines changed: 98 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
from opentelemetry.instrumentation.aiohttp_client import (
4141
AioHttpClientInstrumentor,
4242
)
43-
from opentelemetry.instrumentation.utils import suppress_instrumentation
43+
from opentelemetry.instrumentation.utils import (
44+
suppress_http_instrumentation,
45+
suppress_instrumentation,
46+
)
4447
from opentelemetry.semconv._incubating.attributes.http_attributes import (
4548
HTTP_HOST,
4649
HTTP_METHOD,
@@ -828,6 +831,56 @@ async def request_handler(request):
828831
self._assert_spans([], 0)
829832
self._assert_metrics(0)
830833

834+
def test_metric_attributes_isolation(self):
835+
async def success_handler(request):
836+
assert "traceparent" in request.headers
837+
return aiohttp.web.Response(status=HTTPStatus.OK)
838+
839+
async def timeout_handler(request):
840+
await asyncio.sleep(60)
841+
assert "traceparent" in request.headers
842+
return aiohttp.web.Response(status=HTTPStatus.OK)
843+
844+
trace_config: aiohttp.TraceConfig = (
845+
aiohttp_client.create_trace_config()
846+
)
847+
848+
success_host, success_port = self._http_request(
849+
trace_config=trace_config,
850+
url="/success",
851+
request_handler=success_handler,
852+
)
853+
854+
timeout_host, timeout_port = self._http_request(
855+
trace_config=trace_config,
856+
url="/timeout",
857+
request_handler=timeout_handler,
858+
timeout=aiohttp.ClientTimeout(sock_read=0.01),
859+
)
860+
861+
metrics = self._assert_metrics(1)
862+
duration_dp_attributes = [
863+
dict(dp.attributes) for dp in metrics[0].data.data_points
864+
]
865+
self.assertEqual(
866+
[
867+
{
868+
HTTP_METHOD: "GET",
869+
HTTP_HOST: success_host,
870+
HTTP_STATUS_CODE: int(HTTPStatus.OK),
871+
NET_PEER_NAME: success_host,
872+
NET_PEER_PORT: success_port,
873+
},
874+
{
875+
HTTP_METHOD: "GET",
876+
HTTP_HOST: timeout_host,
877+
NET_PEER_NAME: timeout_host,
878+
NET_PEER_PORT: timeout_port,
879+
},
880+
],
881+
duration_dp_attributes,
882+
)
883+
831884

832885
class TestAioHttpClientInstrumentor(TestBase):
833886
URL = "/test-path"
@@ -1068,33 +1121,60 @@ async def uninstrument_request(server: aiohttp.test_utils.TestServer):
10681121
self._assert_spans(1)
10691122

10701123
def test_suppress_instrumentation(self):
1071-
with suppress_instrumentation():
1072-
run_with_test_server(
1073-
self.get_default_request(), self.URL, self.default_handler
1074-
)
1075-
self._assert_spans(0)
1124+
for suppress_ctx in (
1125+
suppress_instrumentation,
1126+
suppress_http_instrumentation,
1127+
):
1128+
with self.subTest(suppress_ctx=suppress_ctx.__name__):
1129+
with suppress_ctx():
1130+
run_with_test_server(
1131+
self.get_default_request(),
1132+
self.URL,
1133+
self.default_handler,
1134+
)
1135+
self._assert_spans(0)
1136+
self._assert_metrics(0)
10761137

10771138
@staticmethod
1078-
async def suppressed_request(server: aiohttp.test_utils.TestServer):
1079-
async with aiohttp.test_utils.TestClient(server) as client:
1080-
with suppress_instrumentation():
1081-
await client.get(TestAioHttpClientInstrumentor.URL)
1139+
def make_suppressed_request(suppress_ctx):
1140+
async def suppressed_request(server: aiohttp.test_utils.TestServer):
1141+
async with aiohttp.test_utils.TestClient(server) as client:
1142+
with suppress_ctx():
1143+
await client.get(TestAioHttpClientInstrumentor.URL)
1144+
1145+
return suppressed_request
10821146

10831147
def test_suppress_instrumentation_after_creation(self):
1084-
run_with_test_server(
1085-
self.suppressed_request, self.URL, self.default_handler
1086-
)
1087-
self._assert_spans(0)
1148+
for suppress_ctx in (
1149+
suppress_instrumentation,
1150+
suppress_http_instrumentation,
1151+
):
1152+
with self.subTest(suppress_ctx=suppress_ctx.__name__):
1153+
run_with_test_server(
1154+
self.make_suppressed_request(suppress_ctx),
1155+
self.URL,
1156+
self.default_handler,
1157+
)
1158+
self._assert_spans(0)
1159+
self._assert_metrics(0)
10881160

10891161
def test_suppress_instrumentation_with_server_exception(self):
10901162
# pylint:disable=unused-argument
10911163
async def raising_handler(request):
10921164
raise aiohttp.web.HTTPFound(location=self.URL)
10931165

1094-
run_with_test_server(
1095-
self.suppressed_request, self.URL, raising_handler
1096-
)
1097-
self._assert_spans(0)
1166+
for suppress_ctx in (
1167+
suppress_instrumentation,
1168+
suppress_http_instrumentation,
1169+
):
1170+
with self.subTest(suppress_ctx=suppress_ctx.__name__):
1171+
run_with_test_server(
1172+
self.make_suppressed_request(suppress_ctx),
1173+
self.URL,
1174+
raising_handler,
1175+
)
1176+
self._assert_spans(0)
1177+
self._assert_metrics(0)
10981178

10991179
def test_url_filter(self):
11001180
def strip_query_params(url: yarl.URL) -> str:

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,35 @@ def response_hook(span, instance, response):
110110
client = redis.StrictRedis(host="localhost", port=6379)
111111
client.get("my-key")
112112
113+
Suppress Instrumentation
114+
------------------------
115+
116+
You can use the ``suppress_instrumentation`` context manager to prevent instrumentation
117+
from being applied to specific Redis operations. This is useful when you want to avoid
118+
creating spans for internal operations, health checks, or during specific code paths.
119+
120+
.. code:: python
121+
122+
from opentelemetry.instrumentation.redis import RedisInstrumentor
123+
from opentelemetry.instrumentation.utils import suppress_instrumentation
124+
import redis
125+
126+
# Instrument redis
127+
RedisInstrumentor().instrument()
128+
129+
client = redis.StrictRedis(host="localhost", port=6379)
130+
131+
# This will report a span
132+
client.get("my-key")
133+
134+
# This will NOT report a span
135+
with suppress_instrumentation():
136+
client.get("internal-key")
137+
client.set("cache-key", "value")
138+
139+
# This will report a span again
140+
client.get("another-key")
141+
113142
API
114143
---
115144
"""
@@ -134,7 +163,10 @@ def response_hook(span, instance, response):
134163
_set_connection_attributes,
135164
)
136165
from opentelemetry.instrumentation.redis.version import __version__
137-
from opentelemetry.instrumentation.utils import unwrap
166+
from opentelemetry.instrumentation.utils import (
167+
is_instrumentation_enabled,
168+
unwrap,
169+
)
138170
from opentelemetry.semconv._incubating.attributes.db_attributes import (
139171
DB_STATEMENT,
140172
)
@@ -196,6 +228,9 @@ def _traced_execute_command(
196228
args: tuple[Any, ...],
197229
kwargs: dict[str, Any],
198230
) -> R:
231+
if not is_instrumentation_enabled():
232+
return func(*args, **kwargs)
233+
199234
query = _format_command_args(args)
200235
name = _build_span_name(instance, args)
201236
with tracer.start_as_current_span(
@@ -231,6 +266,9 @@ def _traced_execute_pipeline(
231266
args: tuple[Any, ...],
232267
kwargs: dict[str, Any],
233268
) -> R:
269+
if not is_instrumentation_enabled():
270+
return func(*args, **kwargs)
271+
234272
(
235273
command_stack,
236274
resource,
@@ -276,6 +314,9 @@ async def _async_traced_execute_command(
276314
args: tuple[Any, ...],
277315
kwargs: dict[str, Any],
278316
) -> Awaitable[R]:
317+
if not is_instrumentation_enabled():
318+
return await func(*args, **kwargs)
319+
279320
query = _format_command_args(args)
280321
name = _build_span_name(instance, args)
281322

@@ -307,6 +348,9 @@ async def _async_traced_execute_pipeline(
307348
args: tuple[Any, ...],
308349
kwargs: dict[str, Any],
309350
) -> Awaitable[R]:
351+
if not is_instrumentation_enabled():
352+
return await func(*args, **kwargs)
353+
310354
(
311355
command_stack,
312356
resource,

0 commit comments

Comments
 (0)