@@ -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 )
@@ -533,6 +571,7 @@ def __init__(
533571 )
534572 self ._request_hook = request_hook
535573 self ._response_hook = response_hook
574+ self ._excluded_urls = get_excluded_urls ("HTTPX" )
536575
537576 def __enter__ (self ) -> SyncOpenTelemetryTransport :
538577 self ._transport .__enter__ ()
@@ -562,6 +601,12 @@ def handle_request(
562601 method , url , headers , stream , extensions = _extract_parameters (
563602 args , kwargs
564603 )
604+
605+ if self ._excluded_urls and self ._excluded_urls .url_disabled (
606+ _normalize_url (url )
607+ ):
608+ return self ._transport .handle_request (* args , ** kwargs )
609+
565610 method_original = method .decode ()
566611 span_name = _get_default_span_name (method_original )
567612 span_attributes = {}
@@ -726,6 +771,7 @@ def __init__(
726771
727772 self ._request_hook = request_hook
728773 self ._response_hook = response_hook
774+ self ._excluded_urls = get_excluded_urls ("HTTPX" )
729775
730776 async def __aenter__ (self ) -> "AsyncOpenTelemetryTransport" :
731777 await self ._transport .__aenter__ ()
@@ -753,6 +799,12 @@ async def handle_async_request(
753799 method , url , headers , stream , extensions = _extract_parameters (
754800 args , kwargs
755801 )
802+
803+ if self ._excluded_urls and self ._excluded_urls .url_disabled (
804+ _normalize_url (url )
805+ ):
806+ return await self ._transport .handle_async_request (* args , ** kwargs )
807+
756808 method_original = method .decode ()
757809 span_name = _get_default_span_name (method_original )
758810 span_attributes = {}
@@ -900,6 +952,7 @@ def _instrument(self, **kwargs: typing.Any):
900952 if iscoroutinefunction (async_response_hook )
901953 else None
902954 )
955+ excluded_urls = get_excluded_urls ("HTTPX" )
903956
904957 _OpenTelemetrySemanticConventionStability ._initialize ()
905958 sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability ._get_opentelemetry_stability_opt_in_mode (
@@ -948,6 +1001,7 @@ def _instrument(self, **kwargs: typing.Any):
9481001 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9491002 request_hook = request_hook ,
9501003 response_hook = response_hook ,
1004+ excluded_urls = excluded_urls ,
9511005 ),
9521006 )
9531007 wrap_function_wrapper (
@@ -961,6 +1015,7 @@ def _instrument(self, **kwargs: typing.Any):
9611015 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
9621016 async_request_hook = async_request_hook ,
9631017 async_response_hook = async_response_hook ,
1018+ excluded_urls = excluded_urls ,
9641019 ),
9651020 )
9661021
@@ -980,13 +1035,18 @@ def _handle_request_wrapper( # pylint: disable=too-many-locals
9801035 sem_conv_opt_in_mode : _StabilityMode ,
9811036 request_hook : RequestHook ,
9821037 response_hook : ResponseHook ,
1038+ excluded_urls : ExcludeList | None ,
9831039 ):
9841040 if not is_http_instrumentation_enabled ():
9851041 return wrapped (* args , ** kwargs )
9861042
9871043 method , url , headers , stream , extensions = _extract_parameters (
9881044 args , kwargs
9891045 )
1046+
1047+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1048+ return wrapped (* args , ** kwargs )
1049+
9901050 method_original = method .decode ()
9911051 span_name = _get_default_span_name (method_original )
9921052 span_attributes = {}
@@ -1096,13 +1156,18 @@ async def _handle_async_request_wrapper( # pylint: disable=too-many-locals
10961156 sem_conv_opt_in_mode : _StabilityMode ,
10971157 async_request_hook : AsyncRequestHook ,
10981158 async_response_hook : AsyncResponseHook ,
1159+ excluded_urls : ExcludeList | None ,
10991160 ):
11001161 if not is_http_instrumentation_enabled ():
11011162 return await wrapped (* args , ** kwargs )
11021163
11031164 method , url , headers , stream , extensions = _extract_parameters (
11041165 args , kwargs
11051166 )
1167+
1168+ if excluded_urls and excluded_urls .url_disabled (_normalize_url (url )):
1169+ return await wrapped (* args , ** kwargs )
1170+
11061171 method_original = method .decode ()
11071172 span_name = _get_default_span_name (method_original )
11081173 span_attributes = {}
@@ -1274,6 +1339,8 @@ def instrument_client(
12741339 # response_hook already set
12751340 async_response_hook = None
12761341
1342+ excluded_urls = get_excluded_urls ("HTTPX" )
1343+
12771344 if hasattr (client ._transport , "handle_request" ):
12781345 wrap_function_wrapper (
12791346 client ._transport ,
@@ -1286,6 +1353,7 @@ def instrument_client(
12861353 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
12871354 request_hook = request_hook ,
12881355 response_hook = response_hook ,
1356+ excluded_urls = excluded_urls ,
12891357 ),
12901358 )
12911359 for transport in client ._mounts .values ():
@@ -1301,6 +1369,7 @@ def instrument_client(
13011369 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13021370 request_hook = request_hook ,
13031371 response_hook = response_hook ,
1372+ excluded_urls = excluded_urls ,
13041373 ),
13051374 )
13061375 client ._is_instrumented_by_opentelemetry = True
@@ -1316,6 +1385,7 @@ def instrument_client(
13161385 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13171386 async_request_hook = async_request_hook ,
13181387 async_response_hook = async_response_hook ,
1388+ excluded_urls = excluded_urls ,
13191389 ),
13201390 )
13211391 for transport in client ._mounts .values ():
@@ -1331,6 +1401,7 @@ def instrument_client(
13311401 sem_conv_opt_in_mode = sem_conv_opt_in_mode ,
13321402 async_request_hook = async_request_hook ,
13331403 async_response_hook = async_response_hook ,
1404+ excluded_urls = excluded_urls ,
13341405 ),
13351406 )
13361407 client ._is_instrumented_by_opentelemetry = True
0 commit comments