Skip to content
Closed
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
1 change: 1 addition & 0 deletions src/sap_cloud_sdk/core/telemetry/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
ENV_TRACES_EXPORTER = "OTEL_TRACES_EXPORTER"
ENV_OTLP_PROTOCOL = "OTEL_EXPORTER_OTLP_PROTOCOL"
ENV_OTEL_DISABLED = "CLOUD_SDK_OTEL_DISABLED"
ENV_HIGH_CARDINALITY_URLS = "SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS"


def _get_region() -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def __init__(self, wrapped_exporter: SpanExporter):
"GenAI attribute transformer initialized (minimal normalization enabled)"
)

_SUPPRESS_ATTR = "sap.cloud_sdk.suppress"

def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
"""
Export spans after transforming attributes.
Expand All @@ -65,6 +67,8 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
"""
transformed = []
for span in spans:
if span.attributes and span.attributes.get(self._SUPPRESS_ATTR):
continue
try:
transformed.append(self._normalize_attributes(span))
except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,63 @@
import os
import re

from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.trace import Span

from sap_cloud_sdk.core.telemetry.instrumentation.base import LibraryInstrumentor
from sap_cloud_sdk.core.telemetry.instrumentation._registry import register
from sap_cloud_sdk.core.telemetry.config import ENV_HIGH_CARDINALITY_URLS

# Internal attribute that marks a span for suppression at export time.
# The span is still created so that W3C traceparent headers are injected
# into the outgoing request, preserving distributed trace context.
_SUPPRESS_ATTR = "sap.cloud_sdk.suppress"


def _compile_patterns() -> list[re.Pattern]:
raw = os.getenv(ENV_HIGH_CARDINALITY_URLS, "")
patterns = [p.strip() for p in raw.split(",") if p.strip()]
return [re.compile(re.escape(p)) for p in patterns]


_patterns: list[re.Pattern] = _compile_patterns()


def _is_high_cardinality(url: str) -> bool:
return any(p.search(url) for p in _patterns)


def _request_hook(span: Span, request) -> None:
if _is_high_cardinality(str(request.url)):
span.set_attribute(_SUPPRESS_ATTR, True)


async def _async_request_hook(span: Span, request) -> None:
if _is_high_cardinality(str(request.url)):
span.set_attribute(_SUPPRESS_ATTR, True)


_instrumentor = HTTPXClientInstrumentor()


class HttpxInstrumentor(LibraryInstrumentor):
"""Instruments httpx sync and async clients with OTel spans and W3C header propagation."""
"""Instruments httpx sync and async clients with OTel spans and W3C header propagation.

Spans for URLs matching SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS (comma-separated substrings,
unset by default) are marked for suppression at export time. The span is still created so
that W3C traceparent headers propagate to the downstream service.
"""

library_name = "httpx"

def is_instrumented(self) -> bool:
return _instrumentor.is_instrumented_by_opentelemetry

def _instrument(self, **kwargs) -> None:
_instrumentor.instrument()
_instrumentor.instrument(
request_hook=_request_hook,
async_request_hook=_async_request_hook,
)

def _uninstrument(self) -> None:
_instrumentor.uninstrument()
Expand Down
Loading
Loading