Skip to content

fix(telemetry): suppress high-cardinality httpx spans for MCP calls - #287

Closed
jeanscherf wants to merge 2 commits into
mainfrom
fix/httpx-high-cardinality-spans
Closed

fix(telemetry): suppress high-cardinality httpx spans for MCP calls#287
jeanscherf wants to merge 2 commits into
mainfrom
fix/httpx-high-cardinality-spans

Conversation

@jeanscherf

@jeanscherf jeanscherf commented Aug 26, 2026

Copy link
Copy Markdown
Member

Summary

  • Agents making many outbound calls produce traces with tens of thousands of spans because HTTPXClientInstrumentor creates a child span for every httpx request
  • excluded_urls cannot be used as a fix: it skips both span creation and W3C traceparent header injection, which breaks distributed trace context propagation to downstream services
  • Instead, a sync/async request_hook marks spans for URLs matching SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS with sap.cloud_sdk.suppress=True, and GenAIAttributeTransformer filters them at export time — the span is created (so traceparent propagates correctly) but never exported to the collector
  • Suppression is opt-in via env var (no default patterns) — all spans are exported by default so call visibility is preserved

Behaviour

Default (unchanged) With env var set
Spans exported per matching call 1 span 0 spans
W3C traceparent header injected yes yes

Configure via env: SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS=pattern1,pattern2

Test plan

  • tests/core/unit/telemetry/test_genai_attribute_transformer.py — two new tests: spans marked sap.cloud_sdk.suppress=True are filtered; suppressed-only batches pass an empty list to the wrapped exporter
  • All 406 telemetry unit tests pass

cloud-sdk agents making hundreds of MCP calls (e.g. scoring-agent with
2,159 calls) produced monster traces of 60K+ spans because
HTTPXClientInstrumentor creates a child span for every outbound httpx
request regardless of cardinality.

The span must still be created so that the W3C traceparent header is
injected into the outgoing request before the transport runs. Excluded
URLs in the upstream instrumentor skip header injection entirely, which
breaks distributed trace context propagation downstream.

Instead, a request hook marks high-cardinality spans with
sap.cloud_sdk.suppress=True (sync and async), and
GenAIAttributeTransformer filters them at export time. Traceparent
propagates correctly; nothing is sent to the collector for these spans.

URL patterns default to /v1/mcp/ and are overridable via
SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS (comma-separated substrings).
@jeanscherf
jeanscherf requested a review from a team as a code owner August 26, 2026 14:09
@jeanscherf
jeanscherf marked this pull request as draft August 26, 2026 14:16
Remove /v1/mcp/ as the default pattern. By default no spans are
suppressed so MCP call visibility is preserved for all agents.
Deploy SAP_CLOUD_SDK_HIGH_CARDINALITY_URLS=pattern1,pattern2 to opt
specific agents or workloads into suppression.
@jeanscherf

Copy link
Copy Markdown
Member Author

Closing: the httpx instrumentation is working correctly. One span per outbound request is standard OTel behavior. Monster traces from high fan-out agents are a signal about agent design, not an instrumentation bug — suppressing those spans in the SDK would hide useful diagnostic information. The a2a-python-sdk EventQueue polling pattern is a separate issue to be addressed upstream.

@jeanscherf jeanscherf closed this Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant