fix(core): align OTel HTTP semantics with the cross-tracer RFC - #9688
fix(core): align OTel HTTP semantics with the cross-tracer RFC#9688link04 wants to merge 30 commits into
Conversation
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>
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: ec87c53 | Docs | View more details | Give us feedback! |
BenchmarksBenchmark execution time: 2026-08-25 03:03:43 Comparing candidate commit e220a4b in PR branch Found 4 performance improvements and 7 performance regressions! Performance is the same for 2274 metrics, 7 unstable metrics.
|
…ll back on a bad range
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>
There was a problem hiding this comment.
💡 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".
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>
There was a problem hiding this comment.
💡 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".
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>
There was a problem hiding this comment.
💡 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".
Codecov Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
`_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>
There was a problem hiding this comment.
💡 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".
`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>
There was a problem hiding this comment.
💡 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".
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>
There was a problem hiding this comment.
💡 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".
`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>
There was a problem hiding this comment.
💡 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".
`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>
There was a problem hiding this comment.
💡 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".
…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>
There was a problem hiding this comment.
💡 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".
… 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>
There was a problem hiding this comment.
💡 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".
| if (this.DD_TRACE_OTEL_SEMANTICS_ENABLED) { | ||
| setAndTrack(this, 'OTEL_TRACES_EXPORTER', 'otlp') |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
…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>
There was a problem hiding this comment.
💡 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".
`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>
BridgeAR
left a comment
There was a problem hiding this comment.
I did not yet finish the review and I only took a glimpse at it so far :)
| 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 }) | ||
| } | ||
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
crysmags
left a comment
There was a problem hiding this comment.
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) returnThe 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.
…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
|
@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) |
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:
server.portandhttp.response.status_codeas OTLP integers and omits malformed valuesv0, and peer-service defaults off in semantics modehttp.endpointurl.*,server.*, andnetwork.peer.addressattributesHTTP 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.http.methodhttp.request.methodhttp.request.method_originalfor unknown methodshttp.urlurl.fullwith credential redactionhttp.urlurl.path,url.scheme, and optionalurl.queryout.hostserver.addresshttp.urlserver.addressnetwork.destination.portserver.porthttp.urlserver.porthttp.status_codehttp.response.status_codeas an integer over OTLPhttp.useragentuser_agent.originalhttp.client_ipclient.addressnetwork.peer.addresserror.typecontaining the response status when it caused the errorThe conversion also removes the consumed
network.destination.portmetric after emittingserver.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_featureexpectations.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,PrioritySamplerwritesresource.nameand a provenance marker onto the live root span, andweb.setRoutepublisheshttp.routewhen 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
System-tests using the local
express4tracer build:OTEL_SEMANTICS_OTLPOTEL_SEMANTICS_OTLP_CUSTOM_ERROR_STATUSESOTEL_SEMANTICS_OTLP_TRACE_METRICSOTEL_SEMANTICS_OTLP_SAMPLING_RULES