@@ -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