Skip to content

fix(trace-stats): read OTel HTTP names for the status and method dimensions - #2323

Draft
link04 wants to merge 3 commits into
mainfrom
maximo/otel-semantics-tags
Draft

fix(trace-stats): read OTel HTTP names for the status and method dimensions#2323
link04 wants to merge 3 commits into
mainfrom
maximo/otel-semantics-tags

Conversation

@link04

@link04 link04 commented Aug 5, 2026

Copy link
Copy Markdown

What does this PR do?

Makes the client-side stats aggregation key read the OpenTelemetry HTTP attribute names in addition to the Datadog ones, for the two dimensions where the names differ:

Stats dimension Datadog name OTel name
HTTPStatusCode http.status_code http.response.status_code
HTTPMethod http.method http.request.method

One function, BorrowedAggregationKey::from_obfuscated_span in libdd-trace-stats/src/span_concentrator/aggregation.rs. No struct, signature, or wire changes, so the /v0.6/stats payload stays byte-compatible.

Motivation

The tracers are adding an OTel semantics mode behind DD_TRACE_OTEL_SEMANTICS_ENABLED which renames HTTP span attributes to the OTel semantic-convention names. Under that flag the aggregation key finds neither http.status_code nor http.method, so HTTPStatusCode becomes 0 and HTTPMethod becomes empty for every HTTP span, with no error anywhere.

That failure mode is invisible from the span side. The span reports the right status, the stats bucket reports 0, and no span-level assertion can detect the disagreement. Measured on a Django app with the flag on: the resource, hits and errors were all correct and HTTPStatusCode was 0.

http.endpoint and http.route need no equivalent. http.route is already the OTel name, and http.endpoint is Datadog-only and deliberately retained under the flag.

Design notes

  • Datadog name wins. The OTel lookups only run when the Datadog name is absent, so a span using Datadog naming still costs a single map lookup, and a tracer emitting both names during a migration keeps reporting the dimension it already reported.
  • The status code is checked in metrics as well as meta under the OTel name. OTel types http.response.status_code as an int, and libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs routes int attributes to metrics. A meta-only lookup would miss the common case.
  • Not gated on otel_trace_semantics_enabled, deliberately rather than for cost reasons. Gating is cheap: SpanConcentrator::new has two production call sites, both in libdd-data-pipeline where that flag already lives. The reason not to is that this function already reads two naming conventions for a dimension and does it unconditionally: grpc_status_code sweeps rpc.grpc.status_code (OTel) alongside grpc.code and grpc.status.code (Datadog), added on purpose in feat(trace-stats): add grpc status code in the stats bucket key #1701, and http_endpoint falls back to http.route, which is the same key in both conventions. Keying on span content rather than on a tracer-side flag is the established pattern here, and it is also the more robust one for a library that several tracers feed.

One thing to decide before this merges

The http.method fallback changes stats for spans that already carry the OTel name without the flag, and that case is not hypothetical. Both OTel API bridges mirror http.response.status_code onto http.status_code and leave the method alone: dd-trace-py/ddtrace/internal/opentelemetry/span.py maps four keys and the method is not one of them, and dd-trace-js/packages/dd-trace/src/opentelemetry/span-helpers.js mirrors only the status code.

So an app on ddtrace.opentelemetry.TracerProvider with OTel HTTP instrumentation and the flag unset aggregates into one bucket with an empty HTTPMethod today, and would split into per-method buckets after this change. I think populating the dimension is the correct behavior and the split is a fix rather than a regression, but it is a visible change to existing series and it can add key multiplicity against DEFAULT_MAX_ENTRIES_PER_BUCKET, so it should be an explicit call by someone who owns this data rather than a side effect of an OTel-semantics PR.

The status-code fallback does not have this exposure, precisely because the bridges already mirror it. If the method change is unwanted, dropping it leaves HTTPMethod empty under the flag and the rest of the PR stands.

Adjacent gaps, not addressed here

Flagging rather than fixing, since each is a separate decision:

  • libdd-trace-normalization/src/normalizer.rs validates and strips only the Datadog status key, so a span carrying only http.response.status_code skips status-code normalization.
  • libdd-sampling/src/v04_span.rs reads only the Datadog status key and returns None from get_alternate_key, with the comment "v04 spans use Datadog naming conventions natively", which the flag makes false. Concretely: with the flag on, DD_TRACE_SAMPLING_RULES=[{"tags":{"http.status_code":"5??"}}] stops matching on the value lookup and {"tags":{"http.method":"GET"}} stops matching on the key aliasing, so the same flag gives correct stats and silently broken sampling.

Testing

Four cases added to the table-driven test_aggregation_key_from_span: OTel status in metrics, OTel status in meta, both names present asserting Datadog precedence, and OTel method with http.route.

cargo test -p libdd-trace-stats passes 34 tests. cargo clippy -p libdd-trace-stats --all-targets is clean.

Measured against dd-trace-py running a Django app with DD_TRACE_OTEL_SEMANTICS_ENABLED=true, reading the tracer's own /v0.6/stats payload rather than the agent's forwarded one:

{'Name': 'django.request', 'Resource': 'GET status', 'HTTPStatusCode': 0,
 'HTTPMethod': '', 'Hits': 3, 'Errors': 3, 'SpanKind': 'server', 'IsTraceRoot': 1}

Resource, hits, errors, span kind and trace-root are all correct; the two dimensions this PR touches are the only ones lost, which is why the method fallback is here alongside the status code.

With this branch compiled into that same tracer, the same request pattern:

{'Resource': 'GET status', 'HTTPStatusCode': 500, 'HTTPMethod': 'GET', 'Hits': 3, 'Errors': 3}
{'Resource': 'GET status', 'HTTPStatusCode': 200, 'HTTPMethod': 'GET', 'Hits': 3, 'Errors': 0}

The two system-tests cases that compare the span's error decision against the stats bucket (Test_OtelSemantics_Stats_Consistency) go from failing to passing on that change alone.

🤖 Generated with Claude Code

gleocadie and others added 2 commits July 28, 2026 12:36
…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>
@pr-commenter

pr-commenter Bot commented Aug 5, 2026

Copy link
Copy Markdown

Benchmarks

Comparison

Benchmark execution time: 2026-08-06 01:11:31

Comparing candidate commit a142769 in PR branch maximo/otel-semantics-tags with baseline commit 9ac89b9 in branch main.

Found 9 performance improvements and 33 performance regressions! Performance is the same for 98 metrics, 0 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:concentrator/add_spans_to_concentrator

  • 🟩 execution_time [-833.609µs; -826.305µs] or [-8.770%; -8.693%]

scenario:msgpack_decoder::v05/high_sharing/2000

  • 🟥 execution_time [+134.145µs; +135.655µs] or [+8.649%; +8.746%]
  • 🟥 throughput [-103679.201op/s; -102648.491op/s] or [-8.041%; -7.961%]

scenario:normalization/normalize_name/normalize_name/Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Lo...

  • 🟥 execution_time [+19.846µs; +20.000µs] or [+10.685%; +10.767%]
  • 🟥 throughput [-523485.351op/s; -519536.079op/s] or [-9.723%; -9.650%]

scenario:normalization/normalize_name/normalize_name/bad-name

  • 🟥 execution_time [+1.780µs; +1.802µs] or [+10.290%; +10.419%]
  • 🟥 throughput [-5459711.929op/s; -5391760.865op/s] or [-9.442%; -9.325%]

scenario:normalization/normalize_name/normalize_name/good

  • 🟥 execution_time [+1.300µs; +1.333µs] or [+13.170%; +13.507%]
  • 🟥 throughput [-12058614.926op/s; -11779361.729op/s] or [-11.901%; -11.625%]

scenario:normalization/normalize_service/normalize_service/A0000000000000000000000000000000000000000000000000...

  • 🟥 execution_time [+40.395µs; +40.962µs] or [+8.119%; +8.233%]
  • 🟥 throughput [-153061.251op/s; -150833.532op/s] or [-7.615%; -7.504%]

scenario:normalization/normalize_service/normalize_service/Test Conversion 0f Weird !@#$%^&**() Characters

  • 🟥 execution_time [+29.080µs; +29.216µs] or [+17.370%; +17.451%]
  • 🟥 throughput [-887753.668op/s; -883656.688op/s] or [-14.863%; -14.794%]

scenario:normalization/normalize_service/normalize_service/[empty string]

  • 🟥 execution_time [+2.948µs; +2.985µs] or [+8.340%; +8.445%]
  • 🟥 throughput [-2205184.265op/s; -2176687.072op/s] or [-7.794%; -7.693%]

scenario:normalization/normalize_trace/test_trace

  • 🟥 execution_time [+15.108ns; +20.056ns] or [+6.120%; +8.124%]

scenario:otlp/encode_json/1x1000

  • 🟥 execution_time [+121.488µs; +122.748µs] or [+7.340%; +7.416%]

scenario:profile_add_sample_frames_x1000

  • 🟥 execution_time [+172.476µs; +173.288µs] or [+4.271%; +4.291%]

scenario:profile_serialize_compressed_pprof_timestamped_x1000

  • 🟩 execution_time [-41.355µs; -40.386µs] or [-4.222%; -4.123%]

scenario:vec_map/as_deduped_map/already_deduped/8

  • 🟩 execution_time [-0.894ns; -0.863ns] or [-5.694%; -5.493%]

scenario:vec_map/contains_key/128

  • 🟥 execution_time [+817.572ns; +825.467ns] or [+5.475%; +5.528%]
  • 🟥 throughput [-449100.934op/s; -444874.128op/s] or [-5.239%; -5.190%]

scenario:vec_map/contains_key/16

  • 🟥 execution_time [+23.839ns; +24.211ns] or [+10.364%; +10.526%]
  • 🟥 throughput [-6629362.580op/s; -6528178.541op/s] or [-9.530%; -9.385%]

scenario:vec_map/contains_key/64

  • 🟥 execution_time [+198.011ns; +201.873ns] or [+4.977%; +5.074%]
  • 🟥 throughput [-776670.531op/s; -762297.446op/s] or [-4.829%; -4.739%]

scenario:vec_map/contains_key/8

  • 🟥 execution_time [+3.754ns; +3.879ns] or [+5.312%; +5.490%]
  • 🟥 throughput [-5899137.104op/s; -5705715.935op/s] or [-5.211%; -5.040%]

scenario:vec_map/get_hit/128

  • 🟥 execution_time [+2.974µs; +2.983µs] or [+22.011%; +22.077%]
  • 🟥 throughput [-1713977.367op/s; -1708723.971op/s] or [-18.090%; -18.035%]

scenario:vec_map/get_hit/16

  • 🟥 execution_time [+48.501ns; +48.715ns] or [+24.145%; +24.252%]
  • 🟥 throughput [-15557849.957op/s; -15482042.289op/s] or [-19.532%; -19.437%]

scenario:vec_map/get_hit/64

  • 🟥 execution_time [+686.514ns; +690.270ns] or [+18.784%; +18.887%]
  • 🟥 throughput [-2783575.067op/s; -2767701.435op/s] or [-15.896%; -15.805%]

scenario:vec_map/get_hit/8

  • 🟥 execution_time [+12.392ns; +12.448ns] or [+22.972%; +23.076%]
  • 🟥 throughput [-27826240.684op/s; -27688213.988op/s] or [-18.762%; -18.669%]

scenario:vec_map/get_mut/128

  • 🟩 execution_time [-2.216µs; -2.106µs] or [-13.923%; -13.236%]
  • 🟩 throughput [+1230614.966op/s; +1295877.807op/s] or [+15.293%; +16.104%]

scenario:vec_map/get_mut/16

  • 🟩 execution_time [-30.419ns; -20.097ns] or [-10.021%; -6.620%]
  • 🟩 throughput [+3928201.578op/s; +6039458.058op/s] or [+7.399%; +11.376%]

scenario:vec_map/get_mut/64

  • 🟩 execution_time [-534.815ns; -495.354ns] or [-12.168%; -11.270%]
  • 🟩 throughput [+1862081.400op/s; +2016622.846op/s] or [+12.783%; +13.844%]

Benchmark execution time: 2026-08-06 01:24:24

Comparing candidate commit a142769 in PR branch maximo/otel-semantics-tags with baseline commit 9ac89b9 in branch main.

Found 8 performance improvements and 17 performance regressions! Performance is the same for 117 metrics, 10 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:alloc_free/system/16

  • 🟩 execution_time [-1.977ns; -1.901ns] or [-12.172%; -11.703%]

scenario:alloc_free/system/256

  • 🟩 execution_time [-1.882ns; -1.813ns] or [-11.619%; -11.192%]

scenario:alloc_free/system/4096

  • 🟥 execution_time [+25.941ns; +26.071ns] or [+32.465%; +32.628%]

scenario:alloc_free/system/64

  • 🟩 execution_time [-1.878ns; -1.806ns] or [-11.595%; -11.149%]

scenario:credit_card/is_card_number/ 378282246310005

  • 🟥 execution_time [+5.251µs; +5.349µs] or [+7.681%; +7.824%]
  • 🟥 throughput [-1061283.944op/s; -1043050.621op/s] or [-7.255%; -7.130%]

scenario:credit_card/is_card_number/378282246310005

  • 🟥 execution_time [+5.769µs; +5.868µs] or [+8.855%; +9.006%]
  • 🟥 throughput [-1267794.778op/s; -1248136.786op/s] or [-8.260%; -8.132%]

scenario:credit_card/is_card_number/37828224631000521389798

  • 🟥 execution_time [+7.395µs; +7.428µs] or [+16.149%; +16.221%]
  • 🟥 throughput [-3049528.630op/s; -3034495.558op/s] or [-13.965%; -13.896%]

scenario:credit_card/is_card_number_no_luhn/ 378282246310005

  • 🟥 execution_time [+4.922µs; +4.960µs] or [+9.199%; +9.271%]
  • 🟥 throughput [-1585761.919op/s; -1574151.011op/s] or [-8.485%; -8.423%]

scenario:credit_card/is_card_number_no_luhn/378282246310005

  • 🟥 execution_time [+5.202µs; +5.253µs] or [+10.342%; +10.443%]
  • 🟥 throughput [-1879735.391op/s; -1862801.478op/s] or [-9.456%; -9.371%]

scenario:credit_card/is_card_number_no_luhn/37828224631000521389798

  • 🟥 execution_time [+7.378µs; +7.409µs] or [+16.110%; +16.177%]
  • 🟥 throughput [-3041934.232op/s; -3027999.625op/s] or [-13.932%; -13.868%]

scenario:ddsketch_encode/encode_to_vec/clustered_near_zero

  • 🟥 execution_time [+43.795ns; +48.572ns] or [+6.284%; +6.970%]

scenario:ddsketch_encode/encode_to_vec/large_values

  • 🟥 execution_time [+64.082ns; +68.345ns] or [+7.600%; +8.106%]

scenario:ddsketch_encode/encode_to_vec/mixed

  • 🟥 execution_time [+77.943ns; +82.790ns] or [+6.345%; +6.740%]

scenario:ddsketch_read/ordered_bins/clustered_near_zero

  • 🟩 execution_time [-862.449ns; -834.562ns] or [-13.509%; -13.072%]

scenario:ddsketch_read/ordered_bins/large_values

  • 🟩 execution_time [-1.606µs; -1.597µs] or [-18.815%; -18.710%]

scenario:glob_matcher/unicode_exact_match/wall_time

  • 🟥 execution_time [+4.871ns; +4.909ns] or [+6.462%; +6.512%]

scenario:receiver_entry_point/report/2644

  • 🟩 execution_time [-238.988µs; -227.374µs] or [-6.112%; -5.815%]

scenario:sql/obfuscate_sql_string

  • 🟩 execution_time [-19.438µs; -19.146µs] or [-6.377%; -6.281%]

scenario:tags/replace_trace_tags

  • 🟩 execution_time [-112.073ns; -105.162ns] or [-4.531%; -4.251%]

Candidate

Omitted due to size.

Baseline

Omitted due to size.

@dd-octo-sts

dd-octo-sts Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Artifact Size Benchmark Report

aarch64-alpine-linux-musl
Artifact Baseline Commit Change
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.a 87.14 MB 87.15 MB +.01% (+9.60 KB) 🔍
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.so 8.01 MB 8.01 MB 0% (0 B) 👌
aarch64-unknown-linux-gnu
Artifact Baseline Commit Change
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.a 98.41 MB 98.42 MB +0% (+6.60 KB) 👌
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.77 MB 10.77 MB +0% (+8 B) 👌
libdatadog-x64-windows
Artifact Baseline Commit Change
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.dll 26.00 MB 26.00 MB +0% (+2.50 KB) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.lib 89.18 KB 89.18 KB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.pdb 187.95 MB 187.94 MB -0% (-16.00 KB) 👌
/libdatadog-x64-windows/debug/static/datadog_profiling_ffi.lib 979.41 MB 979.20 MB --.02% (-208.97 KB) 💪
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.dll 8.47 MB 8.47 MB +.01% (+1.00 KB) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.lib 89.18 KB 89.18 KB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.pdb 25.05 MB 25.05 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/static/datadog_profiling_ffi.lib 49.85 MB 49.85 MB +0% (+2.47 KB) 👌
libdatadog-x86-windows
Artifact Baseline Commit Change
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.dll 22.64 MB 22.64 MB +.01% (+3.50 KB) 🔍
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.lib 90.58 KB 90.58 KB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.pdb 192.62 MB 192.64 MB +0% (+16.00 KB) 👌
/libdatadog-x86-windows/debug/static/datadog_profiling_ffi.lib 968.48 MB 968.15 MB --.03% (-342.27 KB) 💪
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.dll 6.54 MB 6.54 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.lib 90.58 KB 90.58 KB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.pdb 26.91 MB 26.92 MB +.02% (+8.00 KB) 🔍
/libdatadog-x86-windows/release/static/datadog_profiling_ffi.lib 47.44 MB 47.44 MB +0% (+1.01 KB) 👌
x86_64-alpine-linux-musl
Artifact Baseline Commit Change
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.a 77.73 MB 77.74 MB +0% (+6.29 KB) 👌
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.so 8.91 MB 8.91 MB 0% (0 B) 👌
x86_64-unknown-linux-gnu
Artifact Baseline Commit Change
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.a 93.24 MB 93.24 MB +0% (+1.74 KB) 👌
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.85 MB 10.85 MB +.03% (+3.96 KB) 🔍

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>
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.

2 participants