litellm/tests/test_litellm/integrations/otel
yucheng-berri 0a606cb258
fix(otel): name the RPC system and upstream on MCP tool-call spans (#35857)
* fix(otel): name the RPC system and upstream on MCP tool-call spans

An MCP tool-call span carried only gen_ai.*, mcp.* and litellm.* attributes. A
CLIENT span holding none of the http/db/messaging/rpc families is
unclassifiable, so Elastic APM indexed these spans as span.type=unknown with no
span.subtype at all, and its span-links API then rejected the whole trace with
"Missing required fields (span.subtype)".

MCP frames every message as JSON-RPC 2.0, so the tool-call span now names
rpc.system. It names server.address and server.port alongside it, derived from
the already-redacted mcp_server_resource origin: naming the RPC system makes a
consumer treat the span as a downstream dependency and key that dependency off
the server address, so emitting one without the other labels the dependency
":0".

The tools/list span is left alone. It reaches the callbacks with no upstream
identity, and a listing can span several upstreams, so it has no address to
attach and would produce exactly that ":0" node.

The wire is untouched: streamable MCP still returns HTTP 200 with isError: true.

* fix(otel): drop rpc.system when no MCP upstream address resolved

server.address and server.port come from mcp_server_resource, which is absent
whenever the tool name resolves to no registered server, is None for a stdio
transport that has no host to log, and parses to no host for an IPv6 origin the
redactor rebuilds without its brackets. rpc.system was stamped unconditionally,
so each of those paths emitted it alone and named the dependency ":0", the
outcome the address pair exists to prevent.

Gating the system attribute on a resolved address makes the pairing structural
rather than leaving it to the two extractors happening to agree.

* fix(otel): require a full MCP destination before naming the RPC system

The gate gave rpc.system a resolved address, but not a resolved port. A
host-bearing scheme outside the HTTP(S) default-port map resolves an address
alone, and mcp_servers[].url is not scheme-validated, so an origin like
mcp://host or ws://host reaches the mapper and names the dependency host:0
instead of the :0 the previous commit removed.

Gating on the complete pair closes it, and covers a port of 0 as well.
_upstream_address_port also gets a direct contract test, including the IPv6
origin the redactor rebuilds without brackets.

* fix(otel): do not raise when an MCP origin has an unparseable port

_redact_mcp_resource_url rebuilds the origin without its IPv6 brackets, so a
zone-scoped address leaves a truthy hostname behind that the host check admits:
http://[fe80::1%25eth0]:80 becomes http://fe80::1%25eth0:80, whose hostname is
fe80 and whose port raises ValueError. That propagated out of
MCPToolCallSpanData.from_standard_logging_payload and cost the span.

Reading both halves inside a guard degrades an unparseable origin to no address,
which is already how the mapper treats an unresolvable upstream, and matches the
guard the redactor puts around the same split. The scheme default port drops the
dict literal so the LIT002 ceiling stays put.
2026-08-07 19:22:35 -07:00
..
test_otel_v2_baggage.py feat(otel): allowlist team_metadata sub-keys promoted to baggage (#29442) 2026-06-01 14:02:23 -07:00
test_otel_v2_components.py fix(otel): make OTLP export work against Grafana Cloud (#35060) 2026-07-29 13:43:33 -07:00
test_otel_v2_config_baggage_parenting_guardrails.py feat(otel): typed semconv-aligned OpenTelemetry instrumentation (#28909) 2026-05-29 23:15:27 -07:00
test_otel_v2_dynamic.py fix(otel): one v2 logger owns the global provider; scope tenant OTLP creds per exporter (#30590) 2026-06-19 11:15:29 -07:00
test_otel_v2_emitter.py fix(otel): cap tool-definition attributes so they cannot evict gen_ai.* from the LLM span (#34828) 2026-07-30 12:01:10 -07:00
test_otel_v2_logger.py fix(otel): name the RPC system and upstream on MCP tool-call spans (#35857) 2026-08-07 19:22:35 -07:00
test_otel_v2_metrics.py fix(otel): label retrieval and agent metrics correctly and emit gen_ai.provider.name (#35151) 2026-07-30 13:48:59 -07:00
test_otel_v2_mount.py perf(otel): resolve LITELLM_OTEL_V2 flag once instead of rebuilding settings per call (#30989) 2026-06-22 11:26:42 -07:00
test_otel_v2_multibackend.py feat(otel): typed semconv-aligned OpenTelemetry instrumentation (#28909) 2026-05-29 23:15:27 -07:00
test_otel_v2_presets.py fix(otel): point AgentOps OTLP exporter at otlp.agentops.ai (#31490) 2026-06-26 20:39:39 -07:00
test_otel_v2_sources_of_truth.py fix(otel): name the RPC system and upstream on MCP tool-call spans (#35857) 2026-08-07 19:22:35 -07:00
test_otel_v2_vendor_mappers.py feat(otel): typed semconv-aligned OpenTelemetry instrumentation (#28909) 2026-05-29 23:15:27 -07:00
test_runtime.py perf(otel): memoize per-request lazy import of otel runtime hooks (#31707) 2026-06-30 10:26:20 -07:00