Skip to content

fix(core): align OTel HTTP semantics with the cross-tracer RFC - #9688

Draft
link04 wants to merge 30 commits into
masterfrom
otel-http-semantics-fixes
Draft

fix(core): align OTel HTTP semantics with the cross-tracer RFC#9688
link04 wants to merge 30 commits into
masterfrom
otel-http-semantics-fixes

Conversation

@link04

@link04 link04 commented Aug 4, 2026

Copy link
Copy Markdown

What does this PR do?

Aligns Node.js HTTP tracing with the cross-tracer OpenTelemetry semantic-conventions contract behind DD_TRACE_OTEL_SEMANTICS_ENABLED.

The flag now behaves consistently from capture through OTLP export:

  • emits the agreed server/client OTel HTTP attributes and removes legacy aliases
  • emits server.port and http.response.status_code as OTLP integers and omits malformed values
  • derives low-cardinality HTTP resource names without falling back to URI paths
  • publishes resolved routes early enough for route-aware sampling while supporting method-only sampling before route resolution
  • preserves user-defined resources, including method-prefixed values set by request hooks
  • computes trace stats after semantic normalization so metric dimensions match exported spans
  • forces OTLP trace export, schema v0, and peer-service defaults off in semantics mode
  • retains http.endpoint
  • captures the URL and socket peer required to derive Next.js url.*, server.*, and network.peer.address attributes
  • avoids a null-span crash in route publication

HTTP tag changes

When OpenTelemetry semantics are enabled, the centralized HTTP conversion emits the following tag names and omits the corresponding legacy aliases. Tags not listed here, including http.route, http.endpoint, configured header tags, and unrelated integration tags, retain their existing names.

Concept Datadog semantics OpenTelemetry semantics Applies to
HTTP method http.method http.request.method Server and client
Original HTTP method (not emitted) http.request.method_original for unknown methods Server and client
Client request URL http.url url.full with credential redaction Client
Server request URL http.url url.path, url.scheme, and optional url.query Server
Target host out.host server.address Client
Server address derived from URL Part of http.url server.address Server
Destination port network.destination.port server.port Client
Server port derived from URL Part of http.url server.port Server
HTTP response status http.status_code http.response.status_code as an integer over OTLP Server and client
User agent http.useragent user_agent.original Server and client
Resolved client address http.client_ip client.address Server
Socket peer address (not emitted by the shared HTTP conversion) network.peer.address Server integrations that expose the socket peer
Status-derived error type (not emitted for status-only errors) error.type containing the response status when it caused the error Server and client

The conversion also removes the consumed network.destination.port metric after emitting server.port, preventing both names from appearing in the OTLP span.

No new client/server error-status setting is introduced. Node.js does not expose the cross-tracer status-range environment settings, so those four system tests remain explicit missing_feature expectations.

One deliberate departure from the approved Node.js approach

The design note for this tracer says the transform "transforms only the serialized payload and never the live span", which is what keeps peer.service, AppSec and stats reading Datadog names. This PR keeps that for everything except one scoped exception: under the flag, PrioritySampler writes resource.name and a provenance marker onto the live root span, and web.setRoute publishes http.route when the framework resolves it.

That exception exists because resource-based sampling rules run before the span finishes, so an export-only rename cannot satisfy them; this is the same ordering problem the RFC cites when it recommends renaming at capture time over renaming on export. Both writes are gated on DD_TRACE_OTEL_SEMANTICS_ENABLED, so the default path still never mutates the live span, and the sampler only overwrites a resource it established itself, tracked by the marker, so an application-set or upstream-set value is left alone.

Motivation

Customers migrating from OpenTelemetry should get consistent HTTP names, types, status behavior, sampling inputs, and trace metrics from the tracer. Sampling and stats execute before export, so the behavior cannot be implemented as an export-only rename.

Companion changes:

Testing

  • ESLint on all changed source/spec files: passed
  • focused semantic, route, sampler, web, and span-processor suites: 162 passed
  • focused Next.js request-capture unit: 1 passed

System-tests using the local express4 tracer build:

Scenario Result
OTEL_SEMANTICS_OTLP 30 passed
OTEL_SEMANTICS_OTLP_CUSTOM_ERROR_STATUSES 4 expected missing features
OTEL_SEMANTICS_OTLP_TRACE_METRICS 4 passed
OTEL_SEMANTICS_OTLP_SAMPLING_RULES 2 passed

link04 and others added 5 commits August 3, 2026 23:29
dd-trace-js had no equivalent of DD_TRACE_HTTP_SERVER_ERROR_STATUSES /
DD_TRACE_HTTP_CLIENT_ERROR_STATUSES, so the status codes that mark an HTTP span
as an error were hardcoded in four near-identical copies of getStatusValidator
(web.js, http client, http2 client, next).

Add both options, with their DD_HTTP_* aliases, and consolidate the four copies
into one config-driven helper. The value is kept as the raw range string on the
config object so telemetry reports what the user actually set; the use site
parses it. The single-range case, which covers both defaults and nearly every
user config, compiles to the same two comparisons the hardcoded thresholds used.

Under DD_TRACE_OTEL_SEMANTICS_ENABLED the client default widens from 400-499 to
400-599, because OTel treats a client 5xx as an error and Datadog historically
did not. An explicitly configured range still wins over that widening.

The server default becomes the range 500-599 rather than `code >= 500`, so a
status of 600 or higher is no longer an error. Those are not valid HTTP status
codes and the configured range is now the contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three defects in the export-time rename, all under DD_TRACE_OTEL_SEMANTICS_ENABLED:

`http.endpoint` was being dropped. It is Datadog-only with no OTel equivalent,
and both ASM and endpoint aggregation read it, so it is now retained on the agent
and the OTLP payload alike.

`http.response.status_code` and `server.port` were written into `metrics` as
numbers. The settled cross-tracer representation is a string in `meta` on the
Datadog agent protocol, so they move there. That means the already-stringified
`http.status_code` is reused verbatim instead of being parsed, and the port comes
straight off the parsed URL as digits, so the move also removes work. OTLP is a
typed protocol and still needs an int there; that is handled in the exporter.

`error.type` was derived from hardcoded status thresholds, and worse, the
transform flipped `error` to 1 on a client 5xx. Trace stats read the formatted
span before the transform runs, so the span and its stats disagreed on every
client 5xx. The error decision now belongs entirely to capture time, where the
configured status ranges already live and where the live span, the stats and the
exported payload all see the same value. The transform only labels an error the
span already carries.

The `http.url`-less server case, previously a FIXME, is fixed in the integration
that caused it rather than here: there is nothing in the formatted span to derive
`url.*` from when no integration set the URL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The HTTP rename writes `http.response.status_code` and `server.port` into `meta`,
because the Datadog agent protocol carries every attribute as a string. OTLP is
typed and the semantic conventions type both as ints, so the exporter promotes
them from the meta string when the flag is on.

Keeping a small allowlist in the exporter, rather than also writing a numeric
copy into `metrics`, is what dd-trace-go settled on (DataDog/dd-trace-go#4888).
Duplicating the value would emit the same attribute twice with two different
types. A value that is not an integer is left as a string rather than becoming
NaN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Next.js never set `http.url` on its span, so the export-time rename had nothing
to derive `url.path`, `url.scheme` or `server.*` from and emitted none of them.

Set it in `bindStart` from the same `extractURL` + query-string obfuscation that
`web.addRequestTags` uses, along with `network.peer.address` from the socket,
which is only reachable at that point. Both are gated on the flag so the default
output is unchanged.

Next also hardcoded `code < 500` as its error rule; it now uses the shared
configurable validator like every other HTTP integration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 98.57% (+0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ec87c53 | Docs | View more details | Give us feedback!

@pr-commenter

pr-commenter Bot commented Aug 4, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-25 03:03:43

Comparing candidate commit e220a4b in PR branch otel-http-semantics-fixes with baseline commit 63a6307 in branch master.

📊 Benchmarking dashboard

Found 4 performance improvements and 7 performance regressions! Performance is the same for 2274 metrics, 7 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:plugin-elasticsearch-bulk-index-24

  • 🟩 cpu_user_time [-220.075ms; -80.281ms] or [-6.354%; -2.318%]
  • 🟩 execution_time [-218.807ms; -79.855ms] or [-6.311%; -2.303%]
  • 🟩 instructions [-2.2G instructions; -0.8G instructions] or [-5.773%; -2.201%]
  • 🟩 throughput [+1815.082op/s; +4957.862op/s] or [+2.286%; +6.245%]

scenario:sampling-agent-24

  • 🟥 cpu_user_time [+76.809ms; +201.713ms] or [+3.022%; +7.937%]
  • 🟥 execution_time [+77.164ms; +203.852ms] or [+3.033%; +8.012%]
  • 🟥 instructions [+503.0M instructions; +1384.8M instructions] or [+2.027%; +5.581%]
  • 🟥 throughput [-379425.482op/s; -145929.320op/s] or [-7.995%; -3.075%]

scenario:sampling-agent-26

  • 🟥 cpu_user_time [+55.930ms; +152.720ms] or [+3.483%; +9.511%]
  • 🟥 execution_time [+55.813ms; +153.460ms] or [+3.450%; +9.486%]
  • 🟥 throughput [-708380.500op/s; -263601.916op/s] or [-9.464%; -3.522%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:debugger-line-probe-with-snapshot-minimal-24

  • unstable max_rss_usage [-15567.739KB; +15473.339KB] or [-6.326%; +6.287%]

scenario:debugger-line-probe-without-snapshot-20

  • unstable max_rss_usage [-9.934MB; +14.484MB] or [-5.246%; +7.649%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-379.128ms; +361.870ms] or [-7.908%; +7.548%]
  • unstable execution_time [-382.488ms; +358.938ms] or [-7.859%; +7.375%]
  • unstable throughput [-128008.123op/s; +137870.818op/s] or [-7.410%; +7.980%]

scenario:plugin-graphql-long-with-depth-off-26

  • unstable max_rss_usage [-13.278MB; +5.345MB] or [-8.024%; +3.230%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-5670.322KB; +5323.989KB] or [-7.029%; +6.600%]

link04 and others added 4 commits August 4, 2026 17:47
No tracer ships that name, so aliasing to it invents a legacy spelling that
never existed. DD_HTTP_SERVER_ERROR_STATUSES stays, since dd-trace-java and
dd-trace-ruby do ship it and it is in the public docs.
DD_TRACE_HTTP_SERVER_ERROR_STATUSES and DD_TRACE_HTTP_CLIENT_ERROR_STATUSES
are new configuration for dd-trace-js either way, so they belong in their own
PR rather than riding along with the semconv attribute work.

The shared getStatusValidator helper stays: http, http2, undici, next and web
still need one place that knows a client span errors on 4xx-5xx under OTel
semantics and on 4xx otherwise. It now only resolves defaults, so the range
parser and its spec go with the env vars.
Keep trace-level sampling, HTTP resource naming, stats dimensions, and OTLP typing consistent across capture, sampling, and export.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align capture, sampling, trace metrics, and OTLP export while preserving user-owned resources and framework routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@link04
link04 marked this pull request as ready for review August 18, 2026 18:22
@link04
link04 requested review from a team as code owners August 18, 2026 18:22
@link04
link04 requested review from bojbrook and khanayan123 and removed request for a team August 18, 2026 18:22

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8dbc9b2e3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/datadog-plugin-next/src/index.js Outdated
Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/dd-trace/src/priority_sampler.js Outdated
Comment thread packages/dd-trace/src/span_stats.js Outdated
Use explicit instrumentation provenance through sampling and export so request hooks, route updates, and custom error statuses retain their intended semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 255538b166

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/datadog-plugin-next/src/index.js
Comment thread packages/dd-trace/src/priority_sampler.js Outdated
Preserve manual method-only spans during early sampling and retain resolved Next.js routes during OTel export normalization.

Co-authored-by: Cursor <cursoragent@cursor.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 613b70a00c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/web.js
Comment thread packages/dd-trace/src/config/index.js
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.57%. Comparing base (bf63cf2) to head (ec87c53).

Additional details and impacted files
@@            Coverage Diff            @@
##           master    #9688     +/-   ##
=========================================
  Coverage   98.56%   98.57%             
=========================================
  Files         989      990      +1     
  Lines      148415   148816    +401     
  Branches    12809    14646   +1837     
=========================================
+ Hits       146288   146691    +403     
+ Misses       2127     2125      -2     
Flag Coverage Δ
aiguard 57.68% <53.04%> (+0.06%) ⬆️
aiguard-integration 55.27% <47.25%> (-0.03%) ⬇️
apm-bucket-0 57.50% <53.04%> (+<0.01%) ⬆️
apm-bucket-1 62.78% <68.17%> (+0.08%) ⬆️
apm-bucket-2 62.08% <83.13%> (+0.53%) ⬆️
apm-bucket-3 59.24% <65.10%> (+0.09%) ⬆️
apm-capabilities-tracing 62.56% <80.58%> (+0.09%) ⬆️
apm-integrations-aerospike 55.21% <53.04%> (+0.01%) ⬆️
apm-integrations-confluentinc-kafka-javascript 60.51% <53.04%> (+0.01%) ⬆️
apm-integrations-couchbase 56.09% <53.04%> (+0.01%) ⬆️
apm-integrations-http 61.56% <86.06%> (+0.48%) ⬆️
apm-integrations-kafkajs 61.02% <53.04%> (-0.02%) ⬇️
apm-integrations-next 58.92% <68.92%> (+0.14%) ⬆️
apm-integrations-prisma 57.63% <54.94%> (+0.02%) ⬆️
appsec 71.20% <63.18%> (-0.01%) ⬇️
appsec-express_fastify_graphql 68.61% <66.84%> (+0.03%) ⬆️
appsec-integration 49.49% <51.43%> (+<0.01%) ⬆️
appsec-kafka_ldapjs_lodash 62.61% <61.26%> (+0.01%) ⬆️
appsec-mongodb-core_mongoose_mysql 66.10% <69.23%> (+0.05%) ⬆️
appsec-next 56.16% <68.92%> (+0.13%) ⬆️
appsec-node-serialize_passport_postgres 65.52% <69.23%> (+0.05%) ⬆️
appsec-sourcing_stripe_template 63.96% <65.38%> (+0.03%) ⬆️
debugger 63.72% <62.92%> (+0.03%) ⬆️
instrumentations-bucket-0 51.08% <41.15%> (-0.05%) ⬇️
instrumentations-bucket-1 59.10% <65.10%> (+0.09%) ⬆️
instrumentations-bucket-10 60.13% <61.26%> (+0.05%) ⬆️
instrumentations-bucket-11 60.78% <61.26%> (+0.04%) ⬆️
instrumentations-bucket-12 50.99% <41.15%> (-0.05%) ⬇️
instrumentations-bucket-13 51.86% <43.68%> (-0.03%) ⬇️
instrumentations-bucket-14 51.09% <41.15%> (-0.05%) ⬇️
instrumentations-bucket-2 52.35% <43.68%> (-0.03%) ⬇️
instrumentations-bucket-3 52.98% <43.68%> (-0.04%) ⬇️
instrumentations-bucket-4 58.16% <61.26%> (+0.06%) ⬆️
instrumentations-bucket-5 48.69% <41.15%> (-0.03%) ⬇️
instrumentations-bucket-6 59.72% <66.84%> (+0.09%) ⬆️
instrumentations-bucket-7 51.28% <41.15%> (-0.05%) ⬇️
instrumentations-bucket-8 57.88% <62.92%> (+0.09%) ⬆️
instrumentations-bucket-9 56.66% <53.04%> (+<0.01%) ⬆️
instrumentations-instrumentation-couchbase 49.86% <41.15%> (-0.05%) ⬇️
instrumentations-integration-esbuild 34.12% <30.21%> (+0.01%) ⬆️
llmobs-ai_anthropic_bedrock 62.30% <57.18%> (+<0.01%) ⬆️
llmobs-bucket-1 60.82% <55.91%> (+0.01%) ⬆️
llmobs-openai 61.51% <57.67%> (+0.01%) ⬆️
llmobs-openai-agents_vertex-ai 59.54% <53.70%> (+<0.01%) ⬆️
llmobs-sdk 67.87% <53.04%> (-0.06%) ⬇️
master-coverage 98.57% <100.00%> (?)
openfeature 55.34% <47.25%> (-0.03%) ⬇️
openfeature-unit 53.31% <43.68%> (-0.04%) ⬇️
platform-core_esbuild_instrumentations-misc 40.82% <43.68%> (+0.02%) ⬆️
platform-integration 59.83% <60.09%> (+0.05%) ⬆️
platform-shimmer_unit-guardrails_webpack 38.51% <40.10%> (+0.01%) ⬆️
plugins-browser-bunyan_bullmq_cassandra 60.93% <54.94%> (+<0.01%) ⬆️
plugins-bucket-0 56.35% <57.18%> (+0.04%) ⬆️
plugins-bucket-1 53.37% <43.95%> (-0.03%) ⬇️
plugins-bucket-11 61.35% <61.46%> (+0.06%) ⬆️
plugins-bucket-18 60.86% <54.94%> (+<0.01%) ⬆️
plugins-bucket-19 59.19% <65.10%> (+0.09%) ⬆️
plugins-bucket-20 61.07% <65.10%> (+0.07%) ⬆️
plugins-bucket-4 55.74% <53.04%> (+0.01%) ⬆️
plugins-cookie_cookie-parser_crypto 50.61% <41.15%> (-0.05%) ⬇️
plugins-fastify_fetch_fs 60.47% <80.04%> (+0.56%) ⬆️
plugins-generic-pool_google-cloud-pubsub_grpc 63.53% <63.18%> (+0.05%) ⬆️
plugins-handlebars_hapi_hono 58.04% <62.92%> (+0.09%) ⬆️
plugins-ioredis_knex_langgraph 56.39% <53.04%> (+0.01%) ⬆️
plugins-ldapjs_light-my-request_limitd-client 57.71% <61.26%> (+0.08%) ⬆️
plugins-lodash_mariadb_memcached 58.96% <53.04%> (-0.01%) ⬇️
plugins-moleculer_mongodb_mongodb-core 60.80% <54.94%> (+<0.01%) ⬆️
plugins-mongoose_multer_mysql 58.63% <54.94%> (+0.01%) ⬆️
plugins-mysql2_nats_node-serialize 60.24% <53.04%> (-0.02%) ⬇️
plugins-opensearch_passport-http_pino 58.51% <59.06%> (+0.03%) ⬆️
plugins-postgres_process_pug 57.63% <53.04%> (+<0.01%) ⬆️
plugins-redis_router_sequelize 60.96% <69.23%> (+0.09%) ⬆️
plugins-test-and-upstream-rhea_undici_url 60.91% <72.31%> (+0.46%) ⬆️
plugins-valkey_vm_winston 57.08% <54.94%> (+0.02%) ⬆️
plugins-ws 58.66% <56.61%> (+0.02%) ⬆️
profiling 61.19% <61.26%> (+0.04%) ⬆️
serverless-aws-sdk-aws-sdk 54.34% <57.18%> (+0.04%) ⬆️
serverless-aws-sdk-base-inject-field 50.31% <41.15%> (-0.05%) ⬇️
serverless-aws-sdk-bedrockruntime 54.11% <53.26%> (+0.04%) ⬆️
serverless-aws-sdk-client 55.64% <59.06%> (+0.05%) ⬆️
serverless-aws-sdk-dynamodb 54.93% <53.26%> (+0.03%) ⬆️
serverless-aws-sdk-eventbridge 56.42% <53.26%> (+0.02%) ⬆️
serverless-aws-sdk-kinesis 58.46% <53.26%> (+0.01%) ⬆️
serverless-aws-sdk-lambda 56.65% <53.26%> (+0.02%) ⬆️
serverless-aws-sdk-s3 55.02% <53.26%> (+0.03%) ⬆️
serverless-aws-sdk-serverless-peer-service 59.02% <57.18%> (+0.02%) ⬆️
serverless-aws-sdk-sns 59.25% <53.26%> (+0.01%) ⬆️
serverless-aws-sdk-sqs 59.67% <57.18%> (+0.02%) ⬆️
serverless-aws-sdk-stepfunctions 54.85% <53.26%> (+0.03%) ⬆️
serverless-aws-sdk-util 50.82% <41.15%> (-0.05%) ⬇️
serverless-bucket-0 52.98% <44.61%> (-0.04%) ⬇️
serverless-bucket-1 58.28% <51.37%> (+<0.01%) ⬆️
test-optimization-cucumber 70.57% <62.08%> (+<0.01%) ⬆️
test-optimization-cypress 64.70% <43.68%> (-0.04%) ⬇️
test-optimization-jest 71.94% <61.37%> (-0.15%) ⬇️
test-optimization-mocha 72.04% <62.91%> (+0.04%) ⬆️
test-optimization-playwright-playwright-atr 59.55% <42.29%> (-0.07%) ⬇️
test-optimization-playwright-playwright-efd 60.21% <42.55%> (-0.07%) ⬇️
test-optimization-playwright-playwright-final-status 59.82% <42.29%> (-0.08%) ⬇️
test-optimization-playwright-playwright-impacted-tests 59.93% <42.29%> (+0.07%) ⬆️
test-optimization-playwright-playwright-reporting 60.95% <42.55%> (-0.13%) ⬇️
test-optimization-playwright-playwright-test-management 61.02% <42.55%> (-0.17%) ⬇️
test-optimization-playwright-playwright-test-span 59.59% <42.29%> (-0.13%) ⬇️
test-optimization-selenium 58.71% <45.76%> (-0.19%) ⬇️
test-optimization-testopt 57.44% <41.51%> (+0.02%) ⬆️
test-optimization-vitest 72.71% <57.14%> (-0.02%) ⬇️
test-optimization-vitest-browser 58.54% <41.56%> (-0.07%) ⬇️
test-optimization-webdriverio 64.79% <46.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

link04 and others added 4 commits August 18, 2026 22:41
`_getPriorityFromTags` resolved `root.context()` above both the semantics flag
check and the noop guard, but the sampling root is not always a full span
there: noop spans and the standalone sampler's plain roots reach this path too.
Those threw `TypeError: root?.context is not a function`, which killed sampling
and stopped every trace from flushing.

Resolve the context only once the flag is on and the root actually carries one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`DD_TRACE_OTEL_SEMANTICS_ENABLED` switches trace export onto OTLP, so every
suite that enables it lost its transport to the mock agent and timed out with
`No matching trace received`. That covered the new http/http2/undici/fetch
client cases as well as two already-merged tests, `inferred_proxy.spec.js` and
the express auto-instrumentation integration test.

Fix it once per harness rather than per spec:

- the plugin mock agent reinstalls the Datadog exporter for flag-on suites, so
  they still assert the converted tags over a transport it can receive
- the integration test asserts against the fake agent's OTLP receiver, where
  the span name carries what it previously read from the Datadog resource

Both merged tests keep their original assertions. The OTLP wire format itself
stays covered by system-tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Serverless callers build the web context themselves and never call
`web.startSpan`, so `addRequestTags` first runs from `web.finishSpan`, after the
handler returned. Azure Functions is the HTTP case. Establishing the
instrumentation resource unconditionally there overwrote a resource the handler
had set, and because the same call records the value as instrumentation-owned,
`addResourceTag` then saw its own marker match and replaced it a second time
with the generated method-and-route resource.

Establish it only while the resource is still unowned. The ownership test now
lives in `isInstrumentationOwnedResource`, shared with `addResourceTag` so the
two sites cannot disagree about who owns the name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d the error

The conversion also accepted any status at or above 400, which contradicted the
rule stated immediately above it: `error.type` describes the error the span
already recorded and never infers one. A span the application marked as an
error while returning a status its validator accepts, a hook-flagged 404 under
the default server range for instance, was exported as though the 404 caused
the failure.

Capture time already records that decision explicitly, in `web.addStatusError`
for servers and in the client plugins, so the marker alone is the precise
condition. Two specs asserted the inferred behavior on states production cannot
produce, an errored span with a failing status but no marker; they now carry the
marker, and a new case pins that an application-recorded error leaves
`error.type` unset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74443cf139

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/web.js Outdated
Comment thread packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js Outdated
`Number('')` and `Number(' ')` are 0, so an empty `http.response.status_code` or
`server.port` from a hook or a manual span was exported as `intValue: 0` instead
of being dropped as malformed. `'0x10'` and `'1e2'` coerced just as quietly.
Both keys are unsigned integers, so match exactly those digits.

Also cuts the explanatory comments back to the constraints a reader cannot
recover from the code, and drops the box-drawing banner and the AIDEV-NOTE
marker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a3c7f6cc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/span_stats.js Outdated
Comment thread packages/dd-trace/src/plugins/util/http-error-statuses.js Outdated
The OTLP exporter rejected a status only a coercion would accept while trace
metrics still took it, so under OTel semantics the metrics could aggregate a
status the exported span omitted. `span_stats` also accepted negative numbers.

Both now go through `isCanonicalIntegerAttribute`, which is where the two
representations are already reconciled. '1e2', '0x10', ' 200 ' and '-1' are
malformed in both places.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e5a8f970a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js
`getStatusValidator` had no unit coverage, so nothing recorded which statuses
each span kind treats as errors or how DD_TRACE_HTTP_SERVER_ERROR_STATUSES is
parsed. Pin the last accepted and first rejected value on each boundary,
including 599 and 600, where the conventions' "any other code the client failed
to interpret" keeps both kinds open above the top of the 5xx range.

Also covers the configured-range parsing: single codes, ranges, reversed ranges,
whitespace, replacement rather than addition, and the fallbacks for an
unparseable or non-string value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15aed2b35e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js
`server.port` and `http.response.status_code` are derived into `meta` and
promoted to `intValue` at export, but a numeric value a hook had set stayed in
`metrics` under the same OTel key, so OTLP carried the attribute twice with two
different values while trace metrics read only the derived one. Drop the numeric
copy, the way the consumed `network.destination.port` metric already was.

The exporter also skipped only non-integers on the metrics path, so a negative
port or status was exported although `meta` and `span_stats` both reject one.
Both now use `isCanonicalIntegerAttribute`, and the key set moves next to it so
the exporter cannot drift from the conversion again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2fb027ad9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
…e ownership test

Three sites, one root cause each, batched so the sibling grep runs once.

The conversion bailed whenever a hook had removed both `http.method` and
`http.url`, but finish-time instrumentation had already recorded the status and
user agent, so those kept their Datadog names on a span the layer had otherwise
converted. The provenance marker distinguishes that span from one the layer
never touched, so only the latter returns early.

`isCanonicalIntegerAttribute` accepted any digit string. Past 2^53 `Number`
rounds silently, and a long enough string becomes Infinity, which
`JSON.stringify` writes as `intValue: null`. It now requires a safe integer, with
9007199254740991 and 9007199254740992 pinned either side.

Ownership of the resource name had three separate spellings, in `web.js`, the
sampler, and the conversion itself. They now share one predicate over values.
The sampler keeps its extra requirement that a marker exist, stated as its own
rule rather than hidden inside a second copy of the ownership test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3864c766f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/datadog-plugin-next/src/request-tags.js
Comment thread integration-tests/opentelemetry.spec.js Outdated
… one

Dropping the numeric int-typed metrics was unconditional, so a hook that removes
the legacy tag and supplies the canonical attribute directly lost it: nothing was
derived to replace it. `newMetrics` is now built once `newMeta` is final and drops
a metric only where a replacement exists.

The Next.js request-tag path recorded the URL and socket peer but not the user
agent that `web.addRequestTags` records, so Next server spans carried no
`user_agent.original`.

The express auto-instrumentation test attached its exit listener after the
request, and `ChildProcess` does not replay an `exit` that already fired, so a
child exiting during `getWithRetry` would hang the test until timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec93dd7d1f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment on lines +404 to +405
if (this.DD_TRACE_OTEL_SEMANTICS_ENABLED) {
setAndTrack(this, 'OTEL_TRACES_EXPORTER', 'otlp')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve single-span sampling when forcing OTLP export

When semantics mode is combined with span-sampling rules and the trace-level decision rejects the trace, this forced exporter switch loses every individually selected span: SpanSampler records the matching _dd.span_sampling.* metadata, but OtlpHttpTraceExporter.export() returns solely because the trace priority is below AUTO_KEEP, without inspecting those selections. Before this change, enabling semantics alone retained the agent exporter, which forwards rejected traces for agent-side single-span sampling; either keep that capability or make the OTLP exporter send the selected spans.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that belongs to the OTLP Trace Export mainly, has been added to the RFC as Out of Scope but is been discussed with the OTel Experience Folks, so disregarding but leaving the comment ATM.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the comment given below: #9688 (review)

Comment thread packages/dd-trace/src/config/index.js
…arker

A Lambda without the Datadog extension or the mini agent reaches the backend only
by writing spans to its log for the Forwarder. Forcing OTLP replaced that
transport with the default `localhost:4318`, where nothing listens, so those
spans were lost without a trace. The log transport is now left in place unless an
OTLP endpoint was configured explicitly, alongside the existing Test
Optimization and Electron exemptions, and the Lambda test moves into a predicate
`exporter.js` and the tracer share.

The status-error marker recorded only that some status had failed validation, so a
request hook answering 200 after a rejected 500 had 200 reported as
`error.type`. It now carries the rejected status and is compared against the one
being reported.

Also adds the JSDoc AGENTS.md requires on the functions this branch introduced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ca55a4656

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/dd-trace/src/opentracing/tracer.js Outdated
link04 and others added 2 commits August 19, 2026 15:40
`runHttpRequestHook` had been reduced to `hook(span, arg1, arg2)`, so it was a
call frame on the request-finish path and an extra name on the module surface for
no behavior. Six call sites now invoke the hook directly, and its spec went with
it: asserting that a pass-through passes through tests nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-out

The carve-out checked only OTEL_EXPORTER_OTLP_ENDPOINT, but the exporter is built
from OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, so a Lambda pointed at a collector with
only the trace-specific variable kept the log exporter and never reached it.

`Config` fills in a default for that variable, so the environment is what shows
whether the caller chose one. The whole decision now lives in
`requiresLambdaLogExporter`, next to the Lambda detection it builds on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@link04
link04 requested a review from a team as a code owner August 20, 2026 17:43

@BridgeAR BridgeAR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not yet finish the review and I only took a glimpse at it so far :)

Comment thread packages/datadog-plugin-http/src/client.js Outdated
Comment thread packages/datadog-plugin-http2/src/client.js Outdated
Comment thread packages/datadog-plugin-next/src/index.js Outdated
Comment thread packages/datadog-plugin-undici/src/index.js Outdated
Comment thread packages/datadog-plugin-ws/src/server.js Outdated
Comment on lines +140 to +150
const samplingByTrace = new Map()
for (const span of spans) {
const priority = span.metrics?.[SAMPLING_PRIORITY_KEY]
if (!Number.isFinite(priority)) continue
const key = span.trace_id.toString(16)
const isRoot = !span.parent_id || span.parent_id.equals(ZERO_ID)
const current = samplingByTrace.get(key)
if (current === undefined || (isRoot && !current.isRoot)) {
samplingByTrace.set(key, { priority, isRoot })
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this could be combined in the lower spans.map call so that we do not have to safe the map and do the extra iterations

@link04 link04 Aug 25, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pre-pass is needed because the sampling decision must be resolved before transforming any spans, while the root may appear after its children. Folding it into spans.map() could transform earlier children before seeing the root; correcting them afterward still requires retained state and another pass. Both current passes are linear. The [child, root] ordering is covered in traces.spec.js:208, so I’d prefer to keep this as-is, WDYT?

Comment thread packages/dd-trace/src/plugins/util/http-otel-semantics.js Outdated
Comment thread packages/dd-trace/src/plugins/util/inferred_proxy.js Outdated

@crysmags crysmags left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: preserve single-span sampling with forced OTLP export

OtlpHttpTraceExporter returns before serializing a trace whose priority is rejected. SpanSampler has already marked any individually selected spans with _dd.span_sampling.mechanism, so enabling OTel semantics currently drops them too.

Keep the selected spans when the trace priority is rejected, then serialize that partial trace with OTLP flags: 1 because the inherited trace priority is still rejected:

const spansToExport = priority !== undefined && priority < AUTO_KEEP
  ? spans.filter(span => span.metrics[SPAN_SAMPLING_MECHANISM] !== undefined)
  : spans

if (spansToExport.length === 0) return

The transformer needs a forceSampled path for this filtered case, otherwise it will derive flags: 0 from the rejected root priority. Please add cases for rejected+selected, rejected+none, and kept traces.

@BridgeAR
BridgeAR marked this pull request as draft August 24, 2026 12:40
link04 and others added 3 commits August 24, 2026 22:08
…read

Six span-creation sites built an intermediate object per span just to add
`INSTRUMENTATION_HTTP_RESOURCE` conditionally. The meta or tags literal is now a
variable and the flag adds the one key with a plain assignment.

Covers the http, http2, undici, next and ws plugins plus the inferred proxy. The
unrelated conditional spreads in the inferred proxy are left as they were.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… marker

The marker is set mid-request, so removing it left the span's meta as a V8
dictionary, which is the same demotion the rename below rebuilds a fresh object
to avoid. Measured: deleting a middle property drops a 12-key meta out of fast
properties, while deleting the most recently added one does not, which is why
this looked free.

The rebuild is guarded on the marker actually being present, so the common path
does nothing. Setting the key to undefined instead is not an option: it stays an
own property and the OTLP exporter emits every meta key, so an internal
`_dd.otel.status_error` attribute would reach exported spans.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-fixes

# Conflicts:
#	packages/datadog-plugin-http/src/client.js
#	packages/datadog-plugin-http2/src/client.js
#	packages/datadog-plugin-undici/src/index.js
#	packages/dd-trace/src/plugins/util/web.js
@link04

link04 commented Aug 25, 2026

Copy link
Copy Markdown
Author

@crysmags for your comment this one has been discussed with product as P) we'll address in bulk after this first effort is completed: #9688 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants