Skip to content

Commit 4dd75fd

Browse files
committed
opentelemetry-instrumentation-requests: add ability to capture request and response headers
1 parent 61641aa commit 4dd75fd

File tree

2 files changed

+72
-1
lines changed
  • instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests
  • util/opentelemetry-util-http/src/opentelemetry/util/http

2 files changed

+72
-1
lines changed

instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ def response_hook(span, request_obj, response):
9393
from __future__ import annotations
9494

9595
import functools
96+
import os
9697
import types
9798
from timeit import default_timer
98-
from typing import Any, Callable, Collection, Optional
99+
from typing import Any, Callable, Collection, Mapping, Optional
99100
from urllib.parse import urlparse
100101

101102
from requests.models import PreparedRequest, Response
@@ -150,9 +151,15 @@ def response_hook(span, request_obj, response):
150151
from opentelemetry.trace import SpanKind, Tracer, get_tracer
151152
from opentelemetry.trace.span import Span
152153
from 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
196233
def _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):

util/opentelemetry-util-http/src/opentelemetry/util/http/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE = (
4949
"OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE"
5050
)
51+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST = (
52+
"OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST"
53+
)
54+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE = (
55+
"OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE"
56+
)
5157

5258
OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS = (
5359
"OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS"

0 commit comments

Comments
 (0)