@@ -199,6 +199,24 @@ async def async_response_hook(span, request, response):
199199 response_hook=async_response_hook
200200 )
201201
202+
203+ Configuration
204+ -------------
205+
206+ Exclude lists
207+ *************
208+ To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_HTTPX_EXCLUDED_URLS``
209+ (or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the
210+ URLs.
211+
212+ For example,
213+
214+ ::
215+
216+ export OTEL_PYTHON_HTTPX_EXCLUDED_URLS="client/.*/info,healthcheck"
217+
218+ will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
219+
202220API
203221---
204222"""
@@ -259,7 +277,12 @@ async def async_response_hook(span, request, response):
259277from opentelemetry .trace import SpanKind , Tracer , TracerProvider , get_tracer
260278from opentelemetry .trace .span import Span
261279from opentelemetry .trace .status import StatusCode
262- from opentelemetry .util .http import redact_url , sanitize_method
280+ from opentelemetry .util .http import (
281+ ExcludeList ,
282+ get_excluded_urls ,
283+ redact_url ,
284+ sanitize_method ,
285+ )
263286
264287_logger = logging .getLogger (__name__ )
265288
@@ -304,7 +327,7 @@ def _extract_parameters(
304327 args : tuple [typing .Any , ...], kwargs : dict [str , typing .Any ]
305328) -> tuple [
306329 bytes ,
307- httpx .URL ,
330+ httpx .URL | tuple [ bytes , bytes , int | None , bytes ] ,
308331 httpx .Headers | None ,
309332 httpx .SyncByteStream | httpx .AsyncByteStream | None ,
310333 dict [str , typing .Any ],
@@ -330,6 +353,21 @@ def _extract_parameters(
330353 return method , url , headers , stream , extensions
331354
332355
356+ def _normalize_url (
357+ url : httpx .URL | tuple [bytes , bytes , int | None , bytes ],
358+ ) -> str :
359+ if isinstance (url , tuple ):
360+ scheme , host , port , path = [
361+ part .decode () if isinstance (part , bytes ) else part for part in url
362+ ]
363+ if port :
364+ return f"{ scheme } ://{ host } :{ port } { path } "
365+ else :
366+ return f"{ scheme } ://{ host } { path } "
367+
368+ return str (url )
369+
370+
333371def _inject_propagation_headers (headers , args , kwargs ):
334372 _headers = _prepare_headers (headers )
335373 inject (_headers )
@@ -485,6 +523,7 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport):
485523 right after the span is created
486524 response_hook: A hook that receives the span, request, and response
487525 that is called right before the span ends
526+ excluded_urls: an ExcludeList instance as returned by opentelemetry.util.http.get_excluded_urls
488527 """
489528
490529 def __init__ (
@@ -533,6 +572,7 @@ def __init__(
533572 )
534573 self ._request_hook = request_hook
535574 self ._response_hook = response_hook
575+ self ._excluded_urls = get_excluded_urls ("HTTPX" )
536576
537577 def __enter__ (self ) -> SyncOpenTelemetryTransport :
538578 self ._transport .__enter__ ()
@@ -562,6 +602,12 @@ def handle_request(
562602 method , url , headers , stream , extensions = _extract_parameters (
563603 args , kwargs
564604 )
605+
606+ if self ._excluded_urls and self ._excluded_urls .url_disabled (
607+ _normalize_url (url )
608+ ):
609+ return self ._transport .handle_request (* args , ** kwargs )
610+
565611 method_original = method .decode ()
566612 span_name = _get_default_span_name (method_original )
567613 span_attributes = {}
@@ -676,6 +722,7 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
676722 right after the span is created
677723 response_hook: A hook that receives the span, request, and response
678724 that is called right before the span ends
725+ excluded_urls: an ExcludeList instance as returned by opentelemetry.util.http.get_
679726 """
680727
681728 def __init__ (
@@ -685,6 +732,7 @@ def __init__(
685732 meter_provider : MeterProvider | None = None ,
686733 request_hook : AsyncRequestHook | None = None ,
687734 response_hook : AsyncResponseHook | None = None ,
735+ excluded_urls : ExcludeList | None = None ,
688736 ):
689737 _OpenTelemetrySemanticConventionStability ._initialize ()
690738 self ._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
@@ -726,6 +774,7 @@ def __init__(
726774
727775 self ._request_hook = request_hook
728776 self ._response_hook = response_hook
777+ self ._excluded_urls = excluded_urls
729778
730779 async def __aenter__ (self ) -> "AsyncOpenTelemetryTransport" :
731780 await self ._transport .__aenter__ ()
@@ -753,6 +802,12 @@ async def handle_async_request(
753802 method , url , headers , stream , extensions = _extract_parameters (
754803 args , kwargs
755804 )
805+
806+ if self ._excluded_urls and self ._excluded_urls .url_disabled (
807+ _normalize_url (url )
808+ ):
809+ return await self ._transport .handle_async_request (* args , ** kwargs )
810+
756811 method_original = method .decode ()
757812 span_name = _get_default_span_name (method_original )
758813 span_attributes = {}
@@ -900,6 +955,7 @@ def _instrument(self, **kwargs: typing.Any):
900955 if iscoroutinefunction (async_response_hook )
901956 else None
902957 )
958+ excluded_urls = get_excluded_urls ("HTTPX" )
903959
904960 _OpenTelemetrySemanticConventionStability ._initialize ()
905961 sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
@@ -948,6 +1004,7 @@ def _instrument(self, **kwargs: typing.Any):
9481004 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9491005 request_hook = request_hook ,
9501006 response_hook = response_hook ,
1007+ excluded_urls = excluded_urls ,
9511008 ),
9521009 )
9531010 wrap_function_wrapper (
@@ -961,6 +1018,7 @@ def _instrument(self, **kwargs: typing.Any):
9611018 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9621019 async_request_hook = async_request_hook ,
9631020 async_response_hook = async_response_hook ,
1021+ excluded_urls = excluded_urls ,
9641022 ),
9651023 )
9661024
@@ -980,13 +1038,18 @@ def _handle_request_wrapper( # pylint: disable=too-many-locals
9801038 sem_conv_opt_in_mode : _StabilityMode ,
9811039 request_hook : RequestHook ,
9821040 response_hook : ResponseHook ,
1041+ excluded_urls : ExcludeList | None ,
9831042 ):
9841043 if not is_http_instrumentation_enabled ():
9851044 return wrapped (* args , ** kwargs )
9861045
9871046 method , url , headers , stream , extensions = _extract_parameters (
9881047 args , kwargs
9891048 )
1049+
1050+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1051+ return wrapped (* args , ** kwargs )
1052+
9901053 method_original = method .decode ()
9911054 span_name = _get_default_span_name (method_original )
9921055 span_attributes = {}
@@ -1096,13 +1159,18 @@ async def _handle_async_request_wrapper( # pylint: disable=too-many-locals
10961159 sem_conv_opt_in_mode : _StabilityMode ,
10971160 async_request_hook : AsyncRequestHook ,
10981161 async_response_hook : AsyncResponseHook ,
1162+ excluded_urls : ExcludeList | None ,
10991163 ):
11001164 if not is_http_instrumentation_enabled ():
11011165 return await wrapped (* args , ** kwargs )
11021166
11031167 method , url , headers , stream , extensions = _extract_parameters (
11041168 args , kwargs
11051169 )
1170+
1171+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1172+ return await wrapped (* args , ** kwargs )
1173+
11061174 method_original = method .decode ()
11071175 span_name = _get_default_span_name (method_original )
11081176 span_attributes = {}
@@ -1274,6 +1342,8 @@ def instrument_client(
12741342 # response_hook already set
12751343 async_response_hook = None
12761344
1345+ excluded_urls = get_excluded_urls ("HTTPX" )
1346+
12771347 if hasattr (client ._transport , "handle_request" ):
12781348 wrap_function_wrapper (
12791349 client ._transport ,
@@ -1286,6 +1356,7 @@ def instrument_client(
12861356 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
12871357 request_hook = request_hook ,
12881358 response_hook = response_hook ,
1359+ excluded_urls = excluded_urls ,
12891360 ),
12901361 )
12911362 for transport in client ._mounts .values ():
@@ -1301,6 +1372,7 @@ def instrument_client(
13011372 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13021373 request_hook = request_hook ,
13031374 response_hook = response_hook ,
1375+ excluded_urls = excluded_urls ,
13041376 ),
13051377 )
13061378 client ._is_instrumented_by_opentelemetry = True
@@ -1316,6 +1388,7 @@ def instrument_client(
13161388 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13171389 async_request_hook = async_request_hook ,
13181390 async_response_hook = async_response_hook ,
1391+ excluded_urls = excluded_urls ,
13191392 ),
13201393 )
13211394 for transport in client ._mounts .values ():
@@ -1331,6 +1404,7 @@ def instrument_client(
13311404 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13321405 async_request_hook = async_request_hook ,
13331406 async_response_hook = async_response_hook ,
1407+ excluded_urls = excluded_urls ,
13341408 ),
13351409 )
13361410 client ._is_instrumented_by_opentelemetry = True
0 commit comments