mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-30 21:56:07 +08:00
91 lines
3.6 KiB
Python
91 lines
3.6 KiB
Python
import re
|
|
|
|
from ..internal.logger import get_logger
|
|
from ..utils.http import normalize_header_name
|
|
|
|
log = get_logger(__name__)
|
|
|
|
REQUEST = 'request'
|
|
RESPONSE = 'response'
|
|
|
|
# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags
|
|
# With the exception of '.' in header names which are replaced with '_' to avoid
|
|
# starting a "new object" on the UI.
|
|
NORMALIZE_PATTERN = re.compile(r'([^a-z0-9_\-:/]){1}')
|
|
|
|
|
|
def store_request_headers(headers, span, integration_config):
|
|
"""
|
|
Store request headers as a span's tags
|
|
:param headers: All the request's http headers, will be filtered through the allowlist
|
|
:type headers: dict or list
|
|
:param span: The Span instance where tags will be stored
|
|
:type span: ddtrace.Span
|
|
:param integration_config: An integration specific config object.
|
|
:type integration_config: ddtrace.settings.IntegrationConfig
|
|
"""
|
|
_store_headers(headers, span, integration_config, REQUEST)
|
|
|
|
|
|
def store_response_headers(headers, span, integration_config):
|
|
"""
|
|
Store response headers as a span's tags
|
|
:param headers: All the response's http headers, will be filtered through the allowlist
|
|
:type headers: dict or list
|
|
:param span: The Span instance where tags will be stored
|
|
:type span: ddtrace.Span
|
|
:param integration_config: An integration specific config object.
|
|
:type integration_config: ddtrace.settings.IntegrationConfig
|
|
"""
|
|
_store_headers(headers, span, integration_config, RESPONSE)
|
|
|
|
|
|
def _store_headers(headers, span, integration_config, request_or_response):
|
|
"""
|
|
:param headers: A dict of http headers to be stored in the span
|
|
:type headers: dict or list
|
|
:param span: The Span instance where tags will be stored
|
|
:type span: ddtrace.span.Span
|
|
:param integration_config: An integration specific config object.
|
|
:type integration_config: ddtrace.settings.IntegrationConfig
|
|
"""
|
|
if not isinstance(headers, dict):
|
|
try:
|
|
headers = dict(headers)
|
|
except Exception:
|
|
return
|
|
|
|
if integration_config is None:
|
|
log.debug('Skipping headers tracing as no integration config was provided')
|
|
return
|
|
|
|
for header_name, header_value in headers.items():
|
|
if not integration_config.header_is_traced(header_name):
|
|
continue
|
|
tag_name = _normalize_tag_name(request_or_response, header_name)
|
|
span.set_tag(tag_name, header_value)
|
|
|
|
|
|
def _normalize_tag_name(request_or_response, header_name):
|
|
"""
|
|
Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e
|
|
'http.request.headers.content_type'. Rules applied actual header name are:
|
|
- any letter is converted to lowercase
|
|
- any digit is left unchanged
|
|
- any block of any length of different ASCII chars is converted to a single underscore '_'
|
|
:param request_or_response: The context of the headers: request|response
|
|
:param header_name: The header's name
|
|
:type header_name: str
|
|
:rtype: str
|
|
"""
|
|
# Looking at:
|
|
# - http://www.iana.org/assignments/message-headers/message-headers.xhtml
|
|
# - https://tools.ietf.org/html/rfc6648
|
|
# and for consistency with other language integrations seems safe to assume the following algorithm for header
|
|
# names normalization:
|
|
# - any letter is converted to lowercase
|
|
# - any digit is left unchanged
|
|
# - any block of any length of different ASCII chars is converted to a single underscore '_'
|
|
normalized_name = NORMALIZE_PATTERN.sub('_', normalize_header_name(header_name))
|
|
return 'http.{}.headers.{}'.format(request_or_response, normalized_name)
|