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 @@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3938](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3938))
- `opentelemetry-instrumentation-aiohttp-server`: Support passing `TracerProvider` when instrumenting.
([#3819](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3819))
- `opentelemetry-instrumentation-urllib3`: add ability to capture custom headers
([#4050](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4050))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,97 @@ def response_hook(

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Request header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
Request header names in urllib3 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Response header names in aiohttp are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment
Response header names in urllib3 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 @@ -142,8 +233,15 @@ def response_hook(
)
from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer
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,
ExcludeList,
get_custom_header_attributes,
get_custom_headers,
get_excluded_urls,
normalise_request_header_name,
normalise_response_header_name,
parse_excluded_urls,
sanitize_method,
)
Expand Down Expand Up @@ -278,6 +376,7 @@ def _instrument(self, **kwargs):
response_size_histogram_new = (
create_http_client_response_body_size(meter)
)

_instrument(
tracer,
duration_histogram_old,
Expand All @@ -295,6 +394,24 @@ def _instrument(self, **kwargs):
else parse_excluded_urls(excluded_urls)
),
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
captured_request_headers=kwargs.get(
"captured_request_headers",
get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_REQUEST
),
),
captured_response_headers=kwargs.get(
"captured_response_headers",
get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_CLIENT_RESPONSE
),
),
sensitive_headers=kwargs.get(
"sensitive_headers",
get_custom_headers(
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
),
),
)

def _uninstrument(self, **kwargs):
Expand All @@ -321,7 +438,11 @@ def _instrument(
url_filter: _UrlFilterT = None,
excluded_urls: ExcludeList = 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,
):
# pylint: disable=too-many-locals
def instrumented_urlopen(wrapped, instance, args, kwargs):
if not is_http_instrumentation_enabled():
return wrapped(*args, **kwargs)
Expand All @@ -345,6 +466,15 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
)
_set_http_url(span_attributes, url, sem_conv_opt_in_mode)

span_attributes.update(
get_custom_header_attributes(
headers,
captured_request_headers,
sensitive_headers,
normalise_request_header_name,
)
)

with (
tracer.start_as_current_span(
span_name, kind=SpanKind.CLIENT, attributes=span_attributes
Expand Down Expand Up @@ -402,6 +532,16 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
sem_conv_opt_in_mode,
)

if span.is_recording():
response_headers_to_set = get_custom_header_attributes(
response.headers,
captured_response_headers,
sensitive_headers,
normalise_response_header_name,
)
for header, value in response_headers_to_set.items():
span.set_attribute(header, value)
Comment on lines +536 to +543
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: any reason not to just do this

Suggested change
response_headers_to_set = get_custom_header_attributes(
response.headers,
captured_response_headers,
sensitive_headers,
normalise_response_header_name,
)
for header, value in response_headers_to_set.items():
span.set_attribute(header, value)
span.set_attributes(get_custom_header_attributes(
response.headers,
captured_response_headers,
sensitive_headers,
normalise_response_header_name,
))


return response

wrapt.wrap_function_wrapper(
Expand Down
Loading
Loading