fix(trace-stats): read OTel HTTP names for the status and method dimensions - #2323
fix(trace-stats): read OTel HTTP names for the status and method dimensions#2323link04 wants to merge 9 commits into
Conversation
BenchmarksComparisonBenchmark execution time: 2026-08-19 18:48:54 Comparing candidate commit 5cc262f in PR branch Found 0 performance improvements and 2 performance regressions! Performance is the same for 137 metrics, 0 unstable metrics.
|
Artifact Size Benchmark Reportaarch64-alpine-linux-musl
aarch64-unknown-linux-gnu
libdatadog-x64-windows
libdatadog-x86-windows
x86_64-alpine-linux-musl
x86_64-unknown-linux-gnu
|
…nsions The stats aggregation key reads http.status_code and http.method only. A tracer running with DD_TRACE_OTEL_SEMANTICS_ENABLED emits http.response.status_code and http.request.method instead, so both dimensions silently collapse: HTTPStatusCode becomes 0 and HTTPMethod becomes empty, for every HTTP span, with no error anywhere. The span still reports its status correctly, so the span and the stats disagree and nothing at span level can see it. Both names are now read, Datadog first so a span using Datadog naming still costs a single lookup and a tracer emitting both during a migration keeps the dimension it already reports. The status code is looked up in metrics as well as meta under the OTel name, because OTel types it as an int and the v04 encoder routes int attributes to metrics. http.endpoint and http.route need no equivalent. http.route is already the OTel name, and http.endpoint is Datadog-only and deliberately retained in that mode. Not gated on the otel_trace_semantics_enabled flag in libdd-data-pipeline: that flag is not plumbed into libdd-trace-stats, and threading it in would mean a breaking change to SpanConcentrator::new. The two names cannot legitimately disagree, so reading both unconditionally is safe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-up on the two lookups added in the previous commit. An empty or unparseable value under the Datadog name terminated the search at the default instead of falling through, so meta["http.status_code"] = "" next to a valid http.response.status_code produced a bucket with status 0, which is the same span/stats disagreement this branch set out to fix. Same for an empty http.method shadowing http.request.method, because or_else only fires on None. Both lookups now skip a value that is empty or does not parse, matching what get_grpc_status_code in the same file already does. Also drops the claim that the v04 encoder is what routes the OTel status code into metrics. The concentrator runs before serialization, so the split is whatever the tracer's own setter did. Three cases added: Datadog name in meta against OTel name in metrics, an unparseable Datadog status with a valid OTel one, and an empty Datadog method. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a142769 to
a8e29fd
Compare
📚 Documentation Check Results📦
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: db0d19b | Docs | View more details | Give us feedback! |
🔒 Cargo Deny Results📦
|
Use Agent-compatible HTTP aliases and reject malformed status values so OTLP trace metrics preserve valid dimensions and leave successful status unset. Co-authored-by: Cursor <cursoragent@cursor.com>
Reject malformed numeric HTTP statuses so valid fallbacks remain usable, and leave successful OTLP trace metrics with the unset status required by OTel semantics. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve the trace-level sampling decision once and apply it to every exported OTLP span for both supported input formats. Co-authored-by: Cursor <cursoragent@cursor.com>
Use the same first-priority decision as chunk dropping so every exported OTLP span carries a consistent sampled flag. Co-authored-by: Cursor <cursoragent@cursor.com>
…-tags # Conflicts: # libdd-data-pipeline/tests/test_trace_exporter_otlp_export.rs
`status.code` is a dimension of the metric, not a span status, so omitting it on success does not mean "unset": it gives successful and error cells different attribute sets, and a consumer grouping by `status.code` loses the OK series entirely. Restore the value 9c7a650 dropped, so every data point carries either STATUS_CODE_OK or STATUS_CODE_ERROR. The two tests that were changed to expect no attribute now expect STATUS_CODE_OK and assert it appears exactly once. The guard that stops `additional_metric_tags` from supplying its own `status.code` stays, as do the HTTP alias parsing and malformed-status handling from the same commit. Spans are untouched, so a successful span still exports with status UNSET. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ichinaski
left a comment
There was a problem hiding this comment.
Changes regarding the OTel tags for method and status_code extraction look good.
| for tag in &group.additional_metric_tags { | ||
| if let Some((k, v)) = tag.split_once(':') { | ||
| if attrs.iter().all(|attr| attr["key"].as_str() != Some(k)) { | ||
| if k != "status.code" && attrs.iter().all(|attr| attr["key"].as_str() != Some(k)) { |
There was a problem hiding this comment.
On line 192 we have
attrs.push(kv_str(
"status.code",
if is_error {
STATUS_CODE_ERROR
} else {
STATUS_CODE_OK
},
));isn't this change dead code?
What does this PR do?
Preserves OpenTelemetry HTTP semantics in libdatadog's in-process consumers and OTLP output.
Trace stats and OTLP trace metrics
http.request.methodandhttp.response.status_codestatus.codeunset and prevents additional tags from overriding the computed statusHTTP tag handling
libdatadog does not rename tracer span tags in this PR. It accepts the Datadog and OpenTelemetry names below as equivalent inputs to trace-stat aggregation and emits the canonical OpenTelemetry name in OTLP trace metrics.
http.methodhttp.request.methodhttp.request.methodhttp.status_codehttp.response.status_codehttp.response.status_codeas an integerstatus.code=STATUS_CODE_ERRORfor errors; omitted for successful spansWhen both naming variants are present, the Datadog name wins. Empty or malformed values fall through to the alternate name instead of hiding a valid value.
OTLP sampled flags
Sampling priority is a trace-level decision, but v0.4/v0.5 inputs may attach
_sampling_priority_v1to only one span. The mapper now uses the same first-priority policy asdrop_chunks, treats a missing priority as sampled, and applies one decision to every span in the chunk.This prevents sampled traces from exporting child spans with
flags=0and keeps OTLP flags consistent with the existing chunk-retention policy.No public signature, wire-format, setting, or environment-variable change is introduced.
Motivation
Under
DD_TRACE_OTEL_SEMANTICS_ENABLED, spans use OTel HTTP names before client-side stats are computed. Stats and OTLP metrics must preserve those dimensions, and all spans in an exported trace must carry the same sampled decision.Companion changes:
Testing
cargo fmt --check -p libdd-trace-utilsEnd-to-end with the companion tracer and system-test changes:
OTEL_SEMANTICS_OTLP_TRACE_METRICSOTEL_SEMANTICS_OTLP_SAMPLING_RULES