@@ -93,9 +93,10 @@ def response_hook(span, request_obj, response):
9393from __future__ import annotations
9494
9595import functools
96+ import os
9697import types
9798from timeit import default_timer
98- from typing import Any , Callable , Collection , Optional
99+ from typing import Any , Callable , Collection , Mapping , Optional
99100from urllib .parse import urlparse
100101
101102from requests .models import PreparedRequest , Response
@@ -150,9 +151,15 @@ def response_hook(span, request_obj, response):
150151from opentelemetry .trace import SpanKind , Tracer , get_tracer
151152from opentelemetry .trace .span import Span
152153from opentelemetry .util .http import (
154+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST ,
155+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE ,
156+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS ,
153157 ExcludeList ,
158+ SanitizeValue ,
154159 detect_synthetic_user_agent ,
155160 get_excluded_urls ,
161+ normalise_request_header_name ,
162+ normalise_response_header_name ,
156163 parse_excluded_urls ,
157164 redact_url ,
158165 sanitize_method ,
@@ -191,6 +198,36 @@ def _set_http_status_code_attribute(
191198 )
192199
193200
201+ def _get_custom_header_attributes (
202+ headers : Mapping [str , str | list [str ]] | None ,
203+ captured_headers : list [str ] | None ,
204+ sensitive_headers : list [str ] | None ,
205+ normalize_function : Callable [[str ], str ],
206+ ) -> dict [str , list [str ]]:
207+ """Extract and sanitize HTTP headers for span attributes.
208+
209+ Args:
210+ headers: The HTTP headers to process, either from a request or response.
211+ Can be None if no headers are available.
212+ captured_headers: List of header regexes to capture as span attributes.
213+ If None or empty, no headers will be captured.
214+ sensitive_headers: List of header regexes whose values should be sanitized
215+ (redacted). If None, no sanitization is applied.
216+ normalize_function: Function to normalize header names
217+ (e.g., normalise_request_header_name or normalise_response_header_name).
218+
219+ Returns:
220+ Dictionary of normalized header attribute names to their values
221+ as lists of strings.
222+ """
223+ if not headers or not captured_headers :
224+ return {}
225+ sanitize : SanitizeValue = SanitizeValue (sensitive_headers or ())
226+ return sanitize .sanitize_header_values (
227+ headers , captured_headers , normalize_function
228+ )
229+
230+
194231# pylint: disable=unused-argument
195232# pylint: disable=R0915
196233def _instrument (
@@ -201,6 +238,9 @@ def _instrument(
201238 response_hook : _ResponseHookT = None ,
202239 excluded_urls : ExcludeList | None = None ,
203240 sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
241+ captured_request_headers : list [str ] | None = None ,
242+ captured_response_headers : list [str ] | None = None ,
243+ sensitive_headers : list [str ] | None = None ,
204244):
205245 """Enables tracing of all requests calls that go through
206246 :code:`requests.session.Session.request` (this includes
@@ -258,6 +298,14 @@ def get_or_create_headers():
258298 span_attributes [USER_AGENT_SYNTHETIC_TYPE ] = synthetic_type
259299 if user_agent :
260300 span_attributes [USER_AGENT_ORIGINAL ] = user_agent
301+ span_attributes .update (
302+ _get_custom_header_attributes (
303+ headers ,
304+ captured_request_headers ,
305+ sensitive_headers ,
306+ normalise_request_header_name ,
307+ )
308+ )
261309
262310 metric_labels = {}
263311 _set_http_method (
@@ -350,6 +398,14 @@ def get_or_create_headers():
350398 version_text ,
351399 sem_conv_opt_in_mode ,
352400 )
401+ span_attributes .update (
402+ _get_custom_header_attributes (
403+ result .headers ,
404+ captured_response_headers ,
405+ sensitive_headers ,
406+ normalise_response_header_name ,
407+ )
408+ )
353409 for key , val in span_attributes .items ():
354410 span .set_attribute (key , val )
355411
@@ -501,6 +557,15 @@ def _instrument(self, **kwargs: Any):
501557 else parse_excluded_urls (excluded_urls )
502558 ),
503559 sem_conv_opt_in_mode = semconv_opt_in_mode ,
560+ captured_request_headers = os .environ .get (
561+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
562+ ),
563+ captured_response_headers = os .environ .get (
564+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
565+ ),
566+ sensitive_headers = os .environ .get (
567+ OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
568+ ),
504569 )
505570
506571 def _uninstrument (self , ** kwargs : Any ):
0 commit comments