You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #2711 / #2712. #2712 fixed the PeerPort → HostPort comparison so the gRPC port fallback in exclude_otel_instrumented_services actually fires. That fix is correct, but it activates a pre-existing design problem that #2711 raised and that was never resolved (the issue was auto-closed by the PR merge):
When the OTLP :path cannot be decoded and detection falls back to port matching, a single OTLP export flags the service for BOTH traces AND metrics — because traces and metrics share the same OTLP port (4317). As a result, a service that exports only one signal via its SDK has OBI suppress the other signal too, which nothing else produces → that signal is lost entirely (over-suppression).
Since #2712 this is now live behavior, not dead code.
Root cause (code level)
1. One export span satisfies both predicates
IsExportMetricsSpan and IsExportTracesSpan each accept a span by path OR port (pkg/appolly/app/request/span.go:2143-2159):
When :path == "*" (see §3), the URL matchers return false and both fall through to the port heuristic. But both heuristics converge on the same port (span.go:2069-2124):
sendsMetricsOnGrpcOtelPort → (no METRICS_ENDPOINT) → sendsOnDefaultGrpcOtelPort → OTLP_ENDPOINT / 4317
sendsTracesOnGrpcOtelPort → (no TRACES_ENDPOINT) → sendsOnDefaultGrpcOtelPort → OTLP_ENDPOINT / 4317
With a single generic OTEL_EXPORTER_OTLP_ENDPOINT, HostPort == 4317 makes bothIsExportMetricsSpan and IsExportTracesSpan return true for the same span.
2. Sticky flags accumulate across spans
checkIfExportsOTel (pkg/ebpf/common/pids.go:225) sets one flag per span via if / else if, but the flags are set-once/sticky (EnsureExportsOTel* in pkg/appolly/discover/exec/file.go:220, "returns true if this call flipped the flag"):
Because every export span matches both predicates, the 1st span flips the metrics flag and the 2nd span (metrics already set → short-circuits to the else-if) flips the traces flag. SDKs export both signals periodically, so within seconds both flags are set.
"*" is a sentinel for "OBI could not decode the path", not malformed traffic. OTLP exporters hold a single long-lived HTTP/2 connection and send an identical :path every export, so HPACK compresses it to a dynamic-table index after the first request. OBI attaching mid-stream lacks that dynamic-table state and cannot resolve the index → "*". This is common (and especially frequent with Python grpcio exporters), which is exactly why the port fallback exists.
Reproduction
Service currency exports traces only via OTLP gRPC to a generic endpoint OTEL_EXPORTER_OTLP_ENDPOINT=collector:4317 (no metrics SDK).
Its trace-export client spans arrive with Path="*", HostPort=4317, status UNSET.
IsExportTracesSpan → true (port) ✅ correct.
IsExportMetricsSpan → also true (same port) ❌ misclassification.
ExportsOTelMetrics gets set → metrics.go:896 drops currency's RED metrics.
Net result: currency has no RED metrics anywhere — the SDK never sent them and OBI is now suppressing its own. (Symmetric case: a metrics-only service loses traces.)
Proposed directions
I'd suggest starting with (1) as a near-term safeguard against the over-suppression that #2712 activated, and pursuing (3) as the long-term fix.
Conservative fallback default (near-term). When detection relies on the port-only signal (opaque :path), do not set both flags from a single match; prefer not suppressing when the signal type is ambiguous. In observability, silent data loss is worse than duplication, so the ambiguous case should fail open. This directly contains the regression introduced by Fix OTLP export detection: compare server port (HostPort) not client ephemeral port (PeerPort) #2712 without waiting for a full decoding fix.
Granular suppression. Suppress only overlapping metric names rather than the whole signal type.
Improve :path decoding reliability (long-term). Track per-connection HPACK dynamic-table state so an indexed :path can be resolved, making the port fallback unnecessary and removing the ambiguity at its source.
Summary
Follow-up to #2711 / #2712. #2712 fixed the
PeerPort→HostPortcomparison so the gRPC port fallback inexclude_otel_instrumented_servicesactually fires. That fix is correct, but it activates a pre-existing design problem that #2711 raised and that was never resolved (the issue was auto-closed by the PR merge):When the OTLP
:pathcannot be decoded and detection falls back to port matching, a single OTLP export flags the service for BOTH traces AND metrics — because traces and metrics share the same OTLP port (4317). As a result, a service that exports only one signal via its SDK has OBI suppress the other signal too, which nothing else produces → that signal is lost entirely (over-suppression).Since #2712 this is now live behavior, not dead code.
Root cause (code level)
1. One export span satisfies both predicates
IsExportMetricsSpanandIsExportTracesSpaneach accept a span by path OR port (pkg/appolly/app/request/span.go:2143-2159):When
:path == "*"(see §3), the URL matchers return false and both fall through to the port heuristic. But both heuristics converge on the same port (span.go:2069-2124):With a single generic
OTEL_EXPORTER_OTLP_ENDPOINT,HostPort == 4317makes bothIsExportMetricsSpanandIsExportTracesSpanreturn true for the same span.2. Sticky flags accumulate across spans
checkIfExportsOTel(pkg/ebpf/common/pids.go:225) sets one flag per span viaif / else if, but the flags are set-once/sticky (EnsureExportsOTel*inpkg/appolly/discover/exec/file.go:220, "returns true if this call flipped the flag"):Because every export span matches both predicates, the 1st span flips the metrics flag and the 2nd span (metrics already set → short-circuits to the else-if) flips the traces flag. SDKs export both signals periodically, so within seconds both flags are set.
3. Drop points then suppress both
Once both flags are set, OBI's own RED metrics and traces for that service are dropped.
Why
:pathis"*"(the trigger)pkg/ebpf/common/http2grpc_transform.go:409:"*"is a sentinel for "OBI could not decode the path", not malformed traffic. OTLP exporters hold a single long-lived HTTP/2 connection and send an identical:pathevery export, so HPACK compresses it to a dynamic-table index after the first request. OBI attaching mid-stream lacks that dynamic-table state and cannot resolve the index →"*". This is common (and especially frequent with Pythongrpcioexporters), which is exactly why the port fallback exists.Reproduction
currencyexports traces only via OTLP gRPC to a generic endpointOTEL_EXPORTER_OTLP_ENDPOINT=collector:4317(no metrics SDK).Path="*",HostPort=4317, status UNSET.IsExportTracesSpan→ true (port) ✅ correct.IsExportMetricsSpan→ also true (same port) ❌ misclassification.ExportsOTelMetricsgets set →metrics.go:896dropscurrency's RED metrics.currencyhas no RED metrics anywhere — the SDK never sent them and OBI is now suppressing its own. (Symmetric case: a metrics-only service loses traces.)Proposed directions
I'd suggest starting with (1) as a near-term safeguard against the over-suppression that #2712 activated, and pursuing (3) as the long-term fix.
:path), do not set both flags from a single match; prefer not suppressing when the signal type is ambiguous. In observability, silent data loss is worse than duplication, so the ambiguous case should fail open. This directly contains the regression introduced by Fix OTLP export detection: compare server port (HostPort) not client ephemeral port (PeerPort) #2712 without waiting for a full decoding fix.:pathdecoding reliability (long-term). Track per-connection HPACK dynamic-table state so an indexed:pathcan be resolved, making the port fallback unnecessary and removing the ambiguity at its source.Happy to bring this to the OBI SIG.
Environment
main(post-Fix OTLP export detection: compare server port (HostPort) not client ephemeral port (PeerPort) #2712):pathis HPACK-indexed (notably Pythongrpcio)