@@ -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,22 @@ 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+ return (
364+ f"{ scheme } ://{ host } :{ port } { path } "
365+ if port
366+ else f"{ scheme } ://{ host } { path } "
367+ )
368+
369+ return str (url )
370+
371+
333372def _inject_propagation_headers (headers , args , kwargs ):
334373 _headers = _prepare_headers (headers )
335374 inject (_headers )
@@ -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 = {}
@@ -726,6 +772,7 @@ def __init__(
726772
727773 self ._request_hook = request_hook
728774 self ._response_hook = response_hook
775+ self ._excluded_urls = get_excluded_urls ("HTTPX" )
729776
730777 async def __aenter__ (self ) -> "AsyncOpenTelemetryTransport" :
731778 await self ._transport .__aenter__ ()
@@ -753,6 +800,12 @@ async def handle_async_request(
753800 method , url , headers , stream , extensions = _extract_parameters (
754801 args , kwargs
755802 )
803+
804+ if self ._excluded_urls and self ._excluded_urls .url_disabled (
805+ _normalize_url (url )
806+ ):
807+ return await self ._transport .handle_async_request (* args , ** kwargs )
808+
756809 method_original = method .decode ()
757810 span_name = _get_default_span_name (method_original )
758811 span_attributes = {}
@@ -900,6 +953,7 @@ def _instrument(self, **kwargs: typing.Any):
900953 if iscoroutinefunction (async_response_hook )
901954 else None
902955 )
956+ excluded_urls = get_excluded_urls ("HTTPX" )
903957
904958 _OpenTelemetrySemanticConventionStability ._initialize ()
905959 sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
@@ -948,6 +1002,7 @@ def _instrument(self, **kwargs: typing.Any):
9481002 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9491003 request_hook = request_hook ,
9501004 response_hook = response_hook ,
1005+ excluded_urls = excluded_urls ,
9511006 ),
9521007 )
9531008 wrap_function_wrapper (
@@ -961,6 +1016,7 @@ def _instrument(self, **kwargs: typing.Any):
9611016 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9621017 async_request_hook = async_request_hook ,
9631018 async_response_hook = async_response_hook ,
1019+ excluded_urls = excluded_urls ,
9641020 ),
9651021 )
9661022
@@ -980,13 +1036,18 @@ def _handle_request_wrapper( # pylint: disable=too-many-locals
9801036 sem_conv_opt_in_mode : _StabilityMode ,
9811037 request_hook : RequestHook ,
9821038 response_hook : ResponseHook ,
1039+ excluded_urls : ExcludeList | None ,
9831040 ):
9841041 if not is_http_instrumentation_enabled ():
9851042 return wrapped (* args , ** kwargs )
9861043
9871044 method , url , headers , stream , extensions = _extract_parameters (
9881045 args , kwargs
9891046 )
1047+
1048+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1049+ return wrapped (* args , ** kwargs )
1050+
9901051 method_original = method .decode ()
9911052 span_name = _get_default_span_name (method_original )
9921053 span_attributes = {}
@@ -1096,13 +1157,18 @@ async def _handle_async_request_wrapper( # pylint: disable=too-many-locals
10961157 sem_conv_opt_in_mode : _StabilityMode ,
10971158 async_request_hook : AsyncRequestHook ,
10981159 async_response_hook : AsyncResponseHook ,
1160+ excluded_urls : ExcludeList | None ,
10991161 ):
11001162 if not is_http_instrumentation_enabled ():
11011163 return await wrapped (* args , ** kwargs )
11021164
11031165 method , url , headers , stream , extensions = _extract_parameters (
11041166 args , kwargs
11051167 )
1168+
1169+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1170+ return await wrapped (* args , ** kwargs )
1171+
11061172 method_original = method .decode ()
11071173 span_name = _get_default_span_name (method_original )
11081174 span_attributes = {}
@@ -1198,7 +1264,7 @@ async def _handle_async_request_wrapper( # pylint: disable=too-many-locals
11981264
11991265 return response
12001266
1201- # pylint: disable=too-many-branches
1267+ # pylint: disable=too-many-branches,too-many-locals
12021268 @classmethod
12031269 def instrument_client (
12041270 cls ,
@@ -1274,6 +1340,8 @@ def instrument_client(
12741340 # response_hook already set
12751341 async_response_hook = None
12761342
1343+ excluded_urls = get_excluded_urls ("HTTPX" )
1344+
12771345 if hasattr (client ._transport , "handle_request" ):
12781346 wrap_function_wrapper (
12791347 client ._transport ,
@@ -1286,6 +1354,7 @@ def instrument_client(
12861354 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
12871355 request_hook = request_hook ,
12881356 response_hook = response_hook ,
1357+ excluded_urls = excluded_urls ,
12891358 ),
12901359 )
12911360 for transport in client ._mounts .values ():
@@ -1301,6 +1370,7 @@ def instrument_client(
13011370 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13021371 request_hook = request_hook ,
13031372 response_hook = response_hook ,
1373+ excluded_urls = excluded_urls ,
13041374 ),
13051375 )
13061376 client ._is_instrumented_by_opentelemetry = True
@@ -1316,6 +1386,7 @@ def instrument_client(
13161386 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13171387 async_request_hook = async_request_hook ,
13181388 async_response_hook = async_response_hook ,
1389+ excluded_urls = excluded_urls ,
13191390 ),
13201391 )
13211392 for transport in client ._mounts .values ():
@@ -1331,6 +1402,7 @@ def instrument_client(
13311402 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13321403 async_request_hook = async_request_hook ,
13331404 async_response_hook = async_response_hook ,
1405+ excluded_urls = excluded_urls ,
13341406 ),
13351407 )
13361408 client ._is_instrumented_by_opentelemetry = True
0 commit comments