Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_HTTPX_EXCLUDED_URLS`
([#3850](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3850))

## Version 1.38.0/0.59b0 (2025-10-16)

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
"""
Expand Down Expand Up @@ -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[
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import asyncio
import contextlib
import os
import typing
import unittest
import urllib.parse
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -803,6 +805,24 @@ async def do_request(url):
)
self.memory_exporter.clear()

@mock.patch.dict(
os.environ, {"OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS": "/some/path"}
)
def test_ignores_excluded_urls(self):
async def request_handler(request):
assert "traceparent" not in request.headers
return aiohttp.web.Response(status=HTTPStatus.OK)

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"
Expand Down Expand Up @@ -1115,6 +1135,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):
Expand Down
Loading