Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3967](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3967))
- `opentelemetry-instrumentation-redis`: add missing copyright header for opentelemetry-instrumentation-redis
([#3976](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3976))
- `opentelemetry-instrumentation-aiohttp-client`: add ability to capture custom headers
([#3988](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3988))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,97 @@ def response_hook(span: Span, params: typing.Union[

will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.

Capture HTTP request and response headers
*****************************************
You can configure the agent to capture specified HTTP headers as span attributes, according to the
`semantic conventions <https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span>`_.

Request headers
***************
To capture HTTP request headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to a comma delimited list of HTTP header names.

For example using the environment variable,
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="content-type,custom_request_header"

will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes.

Request header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example:
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST="Accept.*,X-.*"

Would match all request headers that start with ``Accept`` and ``X-``.

To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST`` to ``".*"``.
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST=".*"

The name of the added span attribute will follow the format ``http.request.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
single item list containing all the header values.

For example:
``http.request.header.custom_request_header = ["<value1>", "<value2>"]``

Response headers
****************
To capture HTTP response headers as span attributes, set the environment variable
``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to a comma delimited list of HTTP header names.

For example using the environment variable,
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="content-type,custom_response_header"

will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes.

Response header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
variable will capture the header named ``custom-header``.

Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example:
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE="Content.*,X-.*"

Would match all response headers that start with ``Content`` and ``X-``.

To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE`` to ``".*"``.
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE=".*"

The name of the added span attribute will follow the format ``http.response.header.<header_name>`` where ``<header_name>``
is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a
list containing the header values.

For example:
``http.response.header.custom_response_header = ["<value1>", "<value2>"]``

Sanitizing headers
******************
In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords,
etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS``
to a comma delimited list of HTTP header names to be sanitized.

Regexes may be used, and all header names will be matched in a case-insensitive manner.

For example using the environment variable,
::

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"

will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span.

Note:
The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change.

API
---
"""
Expand Down Expand Up @@ -150,9 +241,16 @@ def response_hook(span: Span, params: typing.Union[
from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import (
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
get_custom_headers,
get_excluded_urls,
normalise_request_header_name,
normalise_response_header_name,
redact_url,
sanitize_method,
get_custom_header_attributes,
)

_UrlFilterT = typing.Optional[typing.Callable[[yarl.URL], str]]
Expand Down Expand Up @@ -215,6 +313,9 @@ def create_trace_config(
tracer_provider: TracerProvider = None,
meter_provider: MeterProvider = None,
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
captured_request_headers: typing.Optional[list[str]] = None,
captured_response_headers: typing.Optional[list[str]] = None,
sensitive_headers: typing.Optional[list[str]] = None,
) -> aiohttp.TraceConfig:
"""Create an aiohttp-compatible trace configuration.

Expand Down Expand Up @@ -243,6 +344,15 @@ def create_trace_config(
:param Callable response_hook: Optional callback that can modify span name and response params.
:param tracer_provider: optional TracerProvider from which to get a Tracer
:param meter_provider: optional Meter provider to use
:param captured_request_headers: List of HTTP request header regexes to capture as
span attributes. Header names matching these patterns will be added as span
attributes with the format ``http.request.header.<header_name>``.
:param captured_response_headers: List of HTTP response header regexes to capture as
span attributes. Header names matching these patterns will be added as span
attributes with the format ``http.response.header.<header_name>``.
:param sensitive_headers: List of HTTP header regexes whose values should be
sanitized (redacted) when captured. Header values matching these patterns
will be replaced with ``[REDACTED]``.

:return: An object suitable for use with :py:class:`aiohttp.ClientSession`.
:rtype: :py:class:`aiohttp.TraceConfig`
Expand Down Expand Up @@ -385,6 +495,18 @@ async def on_request_start(
except ValueError:
pass

span_attributes.update(
get_custom_header_attributes(
{
key: params.headers.getall(key)
for key in params.headers.keys()
},
captured_request_headers,
sensitive_headers,
normalise_request_header_name,
)
)

trace_config_ctx.span = trace_config_ctx.tracer.start_span(
request_span_name, kind=SpanKind.CLIENT, attributes=span_attributes
)
Expand Down Expand Up @@ -415,6 +537,18 @@ async def on_request_end(
sem_conv_opt_in_mode,
)

trace_config_ctx.span.set_attributes(
get_custom_header_attributes(
{
key: params.response.headers.getall(key)
for key in params.response.headers.keys()
},
captured_response_headers,
sensitive_headers,
normalise_response_header_name,
)
)

_end_trace(trace_config_ctx)

async def on_request_exception(
Expand Down Expand Up @@ -475,6 +609,9 @@ def _instrument(
typing.Sequence[aiohttp.TraceConfig]
] = None,
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
captured_request_headers: typing.Optional[list[str]] = None,
captured_response_headers: typing.Optional[list[str]] = None,
sensitive_headers: typing.Optional[list[str]] = None,
):
"""Enables tracing of all ClientSessions

Expand All @@ -496,6 +633,9 @@ def instrumented_init(wrapped, instance, args, kwargs):
tracer_provider=tracer_provider,
meter_provider=meter_provider,
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
captured_request_headers=captured_request_headers,
captured_response_headers=captured_response_headers,
sensitive_headers=sensitive_headers,
)
trace_config._is_instrumented_by_opentelemetry = True
client_trace_configs.append(trace_config)
Expand Down Expand Up @@ -560,6 +700,15 @@ def _instrument(self, **kwargs):
response_hook=kwargs.get("response_hook"),
trace_configs=kwargs.get("trace_configs"),
sem_conv_opt_in_mode=_sem_conv_opt_in_mode,
captured_request_headers=get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
),
captured_response_headers=get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
),
sensitive_headers=get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
),
)

def _uninstrument(self, **kwargs):
Expand Down
Loading
Loading