Skip to content

Commit a986d25

Browse files
herin049xrmx
andauthored
opentelemetry-instrumentation-aiohttp-client: add ability to capture custom headers (#3988)
* opentelemetry-instrumentation-aiohttp-client: add support to capture custom headers * update unit tests * update CHANGELOG.md * add 'get_custom_header_attributes' helper to opentelemetry-util-http * fix formatting issues * fix CHANGELOG.md format * Update instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py * Update instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 8d0bada commit a986d25

3 files changed

Lines changed: 546 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Added
1515

16+
- `opentelemetry-instrumentation-aiohttp-client`: add ability to capture custom headers
17+
([#3988](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3988))
1618
- `opentelemetry-instrumentation-requests`: add ability to capture custom headers
1719
([#3987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3987))
1820
- `opentelemetry-instrumentation-aiohttp-client`: add typechecking for aiohttp-client instrumentation

instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,97 @@ def response_hook(span: Span, params: typing.Union[
9898
9999
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
100100
101+
Capture HTTP request and response headers
102+
*****************************************
103+
You can configure the agent to capture specified HTTP headers as span attributes, according to the
104+
`semantic conventions <https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span>`_.
105+
106+
Request headers
107+
***************
108+
To capture HTTP request headers as span attributes, set the environment variable
109+
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to a comma delimited list of HTTP header names.
110+
111+
For example using the environment variable,
112+
::
113+
114+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="content-type,custom_request_header"
115+
116+
will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes.
117+
118+
Request header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
119+
variable will capture the header named ``custom-header``.
120+
121+
Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example:
122+
::
123+
124+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="Accept.*,X-.*"
125+
126+
Would match all request headers that start with ``Accept`` and ``X-``.
127+
128+
To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to ``".*"``.
129+
::
130+
131+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST=".*"
132+
133+
The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>``
134+
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
135+
single item list containing all the header values.
136+
137+
For example:
138+
``http.request.header.custom_request_header = ["<value1>", "<value2>"]``
139+
140+
Response headers
141+
****************
142+
To capture HTTP response headers as span attributes, set the environment variable
143+
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to a comma delimited list of HTTP header names.
144+
145+
For example using the environment variable,
146+
::
147+
148+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="content-type,custom_response_header"
149+
150+
will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes.
151+
152+
Response header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
153+
variable will capture the header named ``custom-header``.
154+
155+
Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example:
156+
::
157+
158+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="Content.*,X-.*"
159+
160+
Would match all response headers that start with ``Content`` and ``X-``.
161+
162+
To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to ``".*"``.
163+
::
164+
165+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE=".*"
166+
167+
The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>``
168+
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
169+
list containing the header values.
170+
171+
For example:
172+
``http.response.header.custom_response_header = ["<value1>", "<value2>"]``
173+
174+
Sanitizing headers
175+
******************
176+
In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords,
177+
etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS``
178+
to a comma delimited list of HTTP header names to be sanitized.
179+
180+
Regexes may be used, and all header names will be matched in a case-insensitive manner.
181+
182+
For example using the environment variable,
183+
::
184+
185+
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"
186+
187+
will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span.
188+
189+
Note:
190+
The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change.
191+
101192
API
102193
---
103194
"""
@@ -162,7 +253,14 @@ def response_hook(span: Span, params: typing.Union[
162253
from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer
163254
from opentelemetry.trace.status import Status, StatusCode
164255
from opentelemetry.util.http import (
256+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST,
257+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE,
258+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
259+
get_custom_header_attributes,
260+
get_custom_headers,
165261
get_excluded_urls,
262+
normalise_request_header_name,
263+
normalise_response_header_name,
166264
redact_url,
167265
sanitize_method,
168266
)
@@ -244,6 +342,9 @@ def create_trace_config(
244342
tracer_provider: Union[TracerProvider, None] = None,
245343
meter_provider: Union[MeterProvider, None] = None,
246344
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
345+
captured_request_headers: typing.Optional[list[str]] = None,
346+
captured_response_headers: typing.Optional[list[str]] = None,
347+
sensitive_headers: typing.Optional[list[str]] = None,
247348
) -> aiohttp.TraceConfig:
248349
"""Create an aiohttp-compatible trace configuration.
249350
@@ -272,6 +373,15 @@ def create_trace_config(
272373
:param Callable response_hook: Optional callback that can modify span name and response params.
273374
:param tracer_provider: optional TracerProvider from which to get a Tracer
274375
:param meter_provider: optional Meter provider to use
376+
:param captured_request_headers: List of HTTP request header regexes to capture as
377+
span attributes. Header names matching these patterns will be added as span
378+
attributes with the format ``http.request.header.<header_name>``.
379+
:param captured_response_headers: List of HTTP response header regexes to capture as
380+
span attributes. Header names matching these patterns will be added as span
381+
attributes with the format ``http.response.header.<header_name>``.
382+
:param sensitive_headers: List of HTTP header regexes whose values should be
383+
sanitized (redacted) when captured. Header values matching these patterns
384+
will be replaced with ``[REDACTED]``.
275385
276386
:return: An object suitable for use with :py:class:`aiohttp.ClientSession`.
277387
:rtype: :py:class:`aiohttp.TraceConfig`
@@ -422,6 +532,18 @@ async def on_request_start(
422532
except ValueError:
423533
pass
424534

535+
span_attributes.update(
536+
get_custom_header_attributes(
537+
{
538+
key: params.headers.getall(key)
539+
for key in params.headers.keys()
540+
},
541+
captured_request_headers,
542+
sensitive_headers,
543+
normalise_request_header_name,
544+
)
545+
)
546+
425547
trace_config_ctx.span = trace_config_ctx.tracer.start_span(
426548
request_span_name, kind=SpanKind.CLIENT, attributes=span_attributes
427549
)
@@ -452,6 +574,18 @@ async def on_request_end(
452574
sem_conv_opt_in_mode,
453575
)
454576

577+
trace_config_ctx.span.set_attributes(
578+
get_custom_header_attributes(
579+
{
580+
key: params.response.headers.getall(key)
581+
for key in params.response.headers.keys()
582+
},
583+
captured_response_headers,
584+
sensitive_headers,
585+
normalise_response_header_name,
586+
)
587+
)
588+
455589
_end_trace(trace_config_ctx)
456590

457591
async def on_request_exception(
@@ -516,6 +650,9 @@ def _instrument(
516650
typing.Sequence[aiohttp.TraceConfig]
517651
] = None,
518652
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
653+
captured_request_headers: typing.Optional[list[str]] = None,
654+
captured_response_headers: typing.Optional[list[str]] = None,
655+
sensitive_headers: typing.Optional[list[str]] = None,
519656
):
520657
"""Enables tracing of all ClientSessions
521658
@@ -542,6 +679,9 @@ def instrumented_init(
542679
tracer_provider=tracer_provider,
543680
meter_provider=meter_provider,
544681
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
682+
captured_request_headers=captured_request_headers,
683+
captured_response_headers=captured_response_headers,
684+
sensitive_headers=sensitive_headers,
545685
)
546686
setattr(trace_config, "_is_instrumented_by_opentelemetry", True)
547687
client_trace_configs.append(trace_config)
@@ -606,6 +746,15 @@ def _instrument(self, **kwargs: Unpack[InstrumentKwargs]):
606746
response_hook=kwargs.get("response_hook"),
607747
trace_configs=kwargs.get("trace_configs"),
608748
sem_conv_opt_in_mode=_sem_conv_opt_in_mode,
749+
captured_request_headers=get_custom_headers(
750+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
751+
),
752+
captured_response_headers=get_custom_headers(
753+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
754+
),
755+
sensitive_headers=get_custom_headers(
756+
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
757+
),
609758
)
610759

611760
def _uninstrument(self, **kwargs: Unpack[UninstrumentKwargs]):

0 commit comments

Comments
 (0)