diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff6759932..48ac3da74b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- `opentelemetry-instrumentation-aiohttp-client`: add support for url exclusions via `OTEL_PYTHON_EXCLUDED_URLS` / `OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS` + ([#3850](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3850)) + ### Fixed - `opentelemetry-instrumentation-botocore`: Handle dict input in _decode_tool_use for Bedrock streaming 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 c84839deb7..d6869bcd36 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 @@ -84,6 +84,20 @@ def response_hook(span: Span, params: typing.Union[ AioHttpClientInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) +Exclude lists +************* +To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS`` +(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the +URLs. + +For example, + +:: + + export OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS="client/.*/info,healthcheck" + +will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. + API --- """ @@ -135,7 +149,11 @@ def response_hook(span: Span, params: typing.Union[ ) from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer from opentelemetry.trace.status import Status, StatusCode -from opentelemetry.util.http import redact_url, sanitize_method +from opentelemetry.util.http import ( + get_excluded_urls, + redact_url, + sanitize_method, +) _UrlFilterT = typing.Optional[typing.Callable[[yarl.URL], str]] _RequestHookT = typing.Optional[ @@ -271,6 +289,8 @@ def create_trace_config( metric_attributes = {} + excluded_urls = get_excluded_urls("AIOHTTP_CLIENT") + def _end_trace(trace_config_ctx: types.SimpleNamespace): elapsed_time = max(default_timer() - trace_config_ctx.start_time, 0) if trace_config_ctx.token: @@ -304,7 +324,10 @@ async def on_request_start( trace_config_ctx: types.SimpleNamespace, params: aiohttp.TraceRequestStartParams, ): - if not is_instrumentation_enabled(): + if ( + not is_instrumentation_enabled() + or trace_config_ctx.excluded_urls.url_disabled(str(params.url)) + ): trace_config_ctx.span = None return @@ -426,6 +449,7 @@ def _trace_config_ctx_factory(**kwargs): start_time=start_time, duration_histogram_old=duration_histogram_old, duration_histogram_new=duration_histogram_new, + excluded_urls=excluded_urls, **kwargs, ) 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 ec608e6a67..87d75afc70 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 @@ -16,6 +16,7 @@ import asyncio import contextlib +import os import typing import unittest import urllib.parse @@ -87,6 +88,7 @@ async def do_request(): return loop.run_until_complete(do_request()) +# pylint: disable=too-many-public-methods class TestAioHttpIntegration(TestBase): _test_status_codes = ( (HTTPStatus.OK, StatusCode.UNSET), @@ -332,7 +334,7 @@ def test_schema_url(self): span = self.memory_exporter.get_finished_spans()[0] self.assertEqual( - span.instrumentation_info.schema_url, + span.instrumentation_scope.schema_url, "https://opentelemetry.io/schemas/1.11.0", ) self.memory_exporter.clear() @@ -349,7 +351,7 @@ def test_schema_url_new_semconv(self): span = self.memory_exporter.get_finished_spans()[0] self.assertEqual( - span.instrumentation_info.schema_url, + span.instrumentation_scope.schema_url, "https://opentelemetry.io/schemas/1.21.0", ) self.memory_exporter.clear() @@ -366,7 +368,7 @@ def test_schema_url_both_semconv(self): span = self.memory_exporter.get_finished_spans()[0] self.assertEqual( - span.instrumentation_info.schema_url, + span.instrumentation_scope.schema_url, "https://opentelemetry.io/schemas/1.21.0", ) self.memory_exporter.clear() @@ -803,6 +805,29 @@ async def do_request(url): ) self.memory_exporter.clear() + def test_ignores_excluded_urls(self): + async def request_handler(request): + assert "traceparent" not in request.headers + return aiohttp.web.Response(status=HTTPStatus.OK) + + for env_var in ( + "OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS", + "OTEL_PYTHON_EXCLUDED_URLS", + ): + with self.subTest(env_var=env_var): + with mock.patch.dict( + os.environ, {env_var: "/some/path"}, clear=True + ): + self._http_request( + trace_config=aiohttp_client.create_trace_config(), + request_handler=request_handler, + url="/some/path?query=param&other=param2", + status_code=HTTPStatus.OK, + ) + + self._assert_spans([], 0) + self._assert_metrics(0) + class TestAioHttpClientInstrumentor(TestBase): URL = "/test-path" @@ -1115,6 +1140,21 @@ def response_hook( self.assertIn("response_hook_attr", span.attributes) self.assertEqual(span.attributes["response_hook_attr"], "value") + @mock.patch.dict( + os.environ, {"OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS": "/test-path"} + ) + def test_ignores_excluded_urls(self): + # need the env var set at instrument time + AioHttpClientInstrumentor().uninstrument() + AioHttpClientInstrumentor().instrument() + + url = "/test-path?query=params" + run_with_test_server( + self.get_default_request(url), url, self.default_handler + ) + self._assert_spans(0) + self._assert_metrics(0) + class TestLoadingAioHttpInstrumentor(unittest.TestCase): def test_loading_instrumentor(self):