feat(opentelemetry): OTLP trace metrics support#8206
Conversation
Overall package sizeSelf size: 6.45 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.2.0 | 104.26 kB | 843.44 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: aa76c17 | Docs | Datadog PR Page | Give us feedback! |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #8206 +/- ##
==========================================
+ Coverage 93.69% 93.74% +0.05%
==========================================
Files 889 897 +8
Lines 50864 51540 +676
Branches 11834 12036 +202
==========================================
+ Hits 47657 48318 +661
- Misses 3207 3222 +15
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:
|
BenchmarksBenchmark execution time: 2026-07-01 19:56:05 Comparing candidate commit aa76c17 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2246 metrics, 40 unstable metrics.
|
00d2998 to
f8ccbb4
Compare
|
This commit sets |
Export client-computed span stats as OTLP metrics (dd.trace.span.hits, dd.trace.span.errors, dd.trace.span.top_level_hits, dd.trace.span.duration) via a new OtlpStatsExporter alongside the existing Datadog /v0.6/stats exporter. Enabled via DD_TRACE_OTEL_METRICS_ENABLED=true, or auto-enabled when both OTEL_TRACES_EXPORTER=otlp and OTEL_METRICS_EXPORTER=otlp are set. URL and protocol are derived from the OTLP trace export configuration. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
traceMetrics is a computed aggregate derived from OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER, and DD_TRACE_OTEL_METRICS_ENABLED — not a raw user-facing key — so it belongs in INTERNAL_RUNTIME_PROPERTIES alongside sampler and stableConfig. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…ndpoint When hostname is an unbracketed IPv6 address (e.g. ::1), the defaultOtlpBase is http://::1:4318 which is not a valid URL. The new URL() call in the traceMetrics block was the first code path to actually parse the string, causing a TypeError that crashed config construction. Wrap the URL derivation in a try/catch so that a malformed traces endpoint falls back to the localhost default without throwing. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…entry The eslint-config-names-sync rule verifies that every leaf property in TracerOptions (index.d.ts) has a matching configurationNames entry in supported-configurations.json. The entry for DD_TRACE_OTEL_METRICS_ENABLED only had internalPropertyName, which is not checked by the rule. Adding configurationNames: ["traceMetricsEnabled"] ties the two files together and satisfies the lint check. Regenerated config types to match. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…ics contract
Update the OTLP trace-metrics export to match the agreed RFC/system-test contract:
- Rename the enablement env var to OTEL_CLIENT_STATS_COMPUTATION_ENABLED and add
DD_TRACE_OTEL_SEMANTICS_ENABLED (OTel-semantics mode: emit only OTel attributes, no dd.*).
- Emit a single histogram named traces.span.sdk.metrics.duration.
- Map dimensions to OTel attributes (span.name, span.kind, http.*, rpc.* from grpc tags) and
convey errors via OTel status.code; default mode also adds dd.operation.name, dd.span.type,
dd.origin and dd.span.top_level.
- Add telemetry.sdk.{name,language,version} resource attributes and emit process tags as dd.<key>
(default mode only); gate all dd.* resource attributes behind default mode.
- Drive the flush/export cadence from OTEL_METRIC_EXPORT_INTERVAL and drop the
_DD_TRACE_STATS_WRITER_INTERVAL override.
- Read grpc.status.code from span.metrics (numeric) with a meta fallback.
Update unit tests accordingly and regenerate config types.
Co-authored-by: Cursor <[email protected]>
…Scope Partition span-stats data points by service so one OTLP payload can carry multiple services, each as its own InstrumentationScope with service.name, service.version and deployment.environment.name. These move off the resource, which now only carries SDK identity, host.name and dd.* attributes. Fix the trace-metrics flush cadence at 10s (no longer driven by OTEL_METRIC_EXPORT_INTERVAL); the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL overrides it in tests only. Co-authored-by: Cursor <[email protected]>
The generic env applier only reads DD_/OTEL_ prefixed vars, so the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (which starts with _DD_) was never wired into config. Read it explicitly so the test-only flush cadence override takes effect. Co-authored-by: Cursor <[email protected]>
…DDSketch Derive the OTLP duration histogram from each group's DDSketch into the spanmetrics-connector default bounds (in seconds), and drop the duplicate exact-cell accumulator in span_stats. Each group now emits at most two data points (ok/error) with a per-group dd.span.top_level heuristic, mirroring libdatadog. Co-authored-by: Cursor <[email protected]>
Move service.name/service.version/deployment.environment.name onto the OTLP resource (the configured default service), emit a single InstrumentationScope, and add service.name as a data-point attribute only when a span's service differs from the configured default. Thread DD_SERVICE through the processor so the transformer can compare against it. Co-authored-by: Cursor <[email protected]>
The exported OTLP metrics no longer carry an InstrumentationScope: a `dd-trace` scope (name/version) is redundant with the resource's telemetry.sdk.* attributes. The single scopeMetrics omits the scope field. Co-authored-by: Cursor <[email protected]>
…_METRICS_ENABLED Rename the OTLP trace-metric attributes from dd.* to datadog.* (operation.name, span.type, span.top_level, origin, runtime_id, datadog.<process tags>) and rename the enablement env var OTEL_CLIENT_STATS_COMPUTATION_ENABLED -> OTEL_TRACES_SPAN_METRICS_ENABLED. Co-authored-by: Cursor <[email protected]>
…when trace metrics enabled Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
4fbb818 to
393a8e9
Compare
… otelSemanticsEnabled to types span_format.js: rebase conflict resolved to branch's addTag refactor which no longer exists in master — revert to explicit typeof checks while keeping the FR06.3 BUG comment. index.d.ts: rename traceMetricsEnabled -> otlpTraceMetricsEnabled to match supported-configurations.json; add otelSemanticsEnabled (DD_TRACE_OTEL_SEMANTICS_ENABLED). Fixes eslint-config-names-sync errors. Regenerate generated-config-types.d.ts from updated inputs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f306149aa5
ℹ️ 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".
…o status name The dd gRPC plugin sets grpc.status.code as a numeric integer via span.setTag, which span_format.js routes into span.metrics rather than span.meta. SpanAggKey was reading meta only, so rpcStatusCode was always empty for real gRPC spans. Now falls back to span.metrics[GRPC_STATUS_CODE] and translates the integer to the canonical status name (OK, NOT_FOUND, etc.) using the gRPC status code table. Meta string takes priority when both are present. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74aa08110f
ℹ️ 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".
|
@codex review |
…LP metrics When a bucket contained both top-level and measured non-top-level spans, the heuristic (topLevelHits === hits) always resolved to false, causing the OTLP histogram to be emitted as datadog.span.top_level=false and dropping top-level traffic from APM metrics. Adding topLevel as a dimension to SpanAggKey causes top-level and non-top-level spans to bucket separately. Each bucket is now always purely top-level or purely non-top-level, so the attribute is always accurate. The native stats path is unaffected because toJSON() omits topLevel; the Agent merges groups with identical key fields, preserving the same Hits/TopLevelHits totals. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74aa08110f
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fc02ff129
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fc02ff129
ℹ️ 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".
…tats regression The previous fix added topLevel to SpanAggKey to separate top-level and non-top-level spans into distinct buckets. This created a real regression in the native /v0.6/stats path: the Agent's mergeDuplicates() correctly sums Hits/Errors/Duration from duplicate rows but silently drops TopLevelHits from the merged-away entry. If the non-top-level row is processed first and becomes the canonical, TopLevelHits from the top-level row is lost. Fix: revert topLevel from SpanAggKey (no more duplicate rows). Instead, split SpanAggStats into four distributions (topLevelOk, topLevelError, nonTopLevelOk, nonTopLevelError). The native stats path merges them at export time so toJSON() produces the same combined OkSummary/ErrorSummary as before. The OTLP path emits separate data points per top-level status with the correct datadog.span.top_level attribute. OTel-semantics mode merges the distributions (no top-level attribute to distinguish them). Also adds SpanKind, Origin, and RpcStatusCode to the native stats toJSON() payload so the Agent receives these new dimensions. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… use GRPCStatusCode key toJSON() now returns an array of up to 2 rows (top-level row first, non-top-level row second). #toLegacyPayload uses flatMap to flatten them. This eliminates the merge-time DDSketch allocation and ensures TopLevelHits is always non-zero on the top-level row, so the Agent's mergeDuplicates retains it as the canonical entry. Duration and Errors are derived from distribution .sum/.count, removing the redundant this.duration and this.errors accumulators. GRPCStatusCode matches the agent's msgpack decoder key (confirmed from pkg/proto/pbgo/trace/stats_gen.go). Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…nstants - Remove descriptive/narrating comments throughout; keep only non-obvious constraints - Move GRPC_STATUS_NAMES from span_stats.js into constants.js - Rename #toLegacyPayload -> #v06Payload - Remove section comment from ext/tags.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… origin from aggregation key ORIGIN_KEY now only populates SpanAggKey.synthetics. The origin string field is removed from SpanAggKey, toString(), and the v0.6 payload (Origin is not a field the agent decodes). In the OTLP path, datadog.origin='synthetics' is emitted when aggKey.synthetics is true. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa76c17d9e
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa76c17d9e
ℹ️ 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".
* feat(span-stats): add OTLP metrics export for span stats Export client-computed span stats as OTLP metrics (dd.trace.span.hits, dd.trace.span.errors, dd.trace.span.top_level_hits, dd.trace.span.duration) via a new OtlpStatsExporter alongside the existing Datadog /v0.6/stats exporter. Enabled via DD_TRACE_OTEL_METRICS_ENABLED=true, or auto-enabled when both OTEL_TRACES_EXPORTER=otlp and OTEL_METRICS_EXPORTER=otlp are set. URL and protocol are derived from the OTLP trace export configuration. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): register traceMetrics as internal runtime property traceMetrics is a computed aggregate derived from OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER, and DD_TRACE_OTEL_METRICS_ENABLED — not a raw user-facing key — so it belongs in INTERNAL_RUNTIME_PROPERTIES alongside sampler and stableConfig. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): guard traceMetrics URL derivation against invalid OTLP endpoint When hostname is an unbracketed IPv6 address (e.g. ::1), the defaultOtlpBase is http://::1:4318 which is not a valid URL. The new URL() call in the traceMetrics block was the first code path to actually parse the string, causing a TypeError that crashed config construction. Wrap the URL derivation in a try/catch so that a malformed traces endpoint falls back to the localhost default without throwing. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(config): regenerate config types for DD_TRACE_OTEL_METRICS_ENABLED Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): add configurationNames to DD_TRACE_OTEL_METRICS_ENABLED entry The eslint-config-names-sync rule verifies that every leaf property in TracerOptions (index.d.ts) has a matching configurationNames entry in supported-configurations.json. The entry for DD_TRACE_OTEL_METRICS_ENABLED only had internalPropertyName, which is not checked by the rule. Adding configurationNames: ["traceMetricsEnabled"] ties the two files together and satisfies the lint check. Regenerated config types to match. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * bug fix * update post RFC discussion * bring implementation bug fix * clean how cinfgs are set * feat(otlp-trace-metrics): align span-stats export with the trace-metrics contract Update the OTLP trace-metrics export to match the agreed RFC/system-test contract: - Rename the enablement env var to OTEL_CLIENT_STATS_COMPUTATION_ENABLED and add DD_TRACE_OTEL_SEMANTICS_ENABLED (OTel-semantics mode: emit only OTel attributes, no dd.*). - Emit a single histogram named traces.span.sdk.metrics.duration. - Map dimensions to OTel attributes (span.name, span.kind, http.*, rpc.* from grpc tags) and convey errors via OTel status.code; default mode also adds dd.operation.name, dd.span.type, dd.origin and dd.span.top_level. - Add telemetry.sdk.{name,language,version} resource attributes and emit process tags as dd.<key> (default mode only); gate all dd.* resource attributes behind default mode. - Drive the flush/export cadence from OTEL_METRIC_EXPORT_INTERVAL and drop the _DD_TRACE_STATS_WRITER_INTERVAL override. - Read grpc.status.code from span.metrics (numeric) with a meta fallback. Update unit tests accordingly and regenerate config types. Co-authored-by: Cursor <[email protected]> * feat(otlp-trace-metrics): report service identity per InstrumentationScope Partition span-stats data points by service so one OTLP payload can carry multiple services, each as its own InstrumentationScope with service.name, service.version and deployment.environment.name. These move off the resource, which now only carries SDK identity, host.name and dd.* attributes. Fix the trace-metrics flush cadence at 10s (no longer driven by OTEL_METRIC_EXPORT_INTERVAL); the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL overrides it in tests only. Co-authored-by: Cursor <[email protected]> * fix(otlp-trace-metrics): apply internal flush interval override The generic env applier only reads DD_/OTEL_ prefixed vars, so the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (which starts with _DD_) was never wired into config. Read it explicitly so the test-only flush cadence override takes effect. Co-authored-by: Cursor <[email protected]> * feat(otlp-span-stats): emit fixed explicit-bounds histogram from the DDSketch Derive the OTLP duration histogram from each group's DDSketch into the spanmetrics-connector default bounds (in seconds), and drop the duplicate exact-cell accumulator in span_stats. Each group now emits at most two data points (ok/error) with a per-group dd.span.top_level heuristic, mirroring libdatadog. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): carry service identity as resource attributes Move service.name/service.version/deployment.environment.name onto the OTLP resource (the configured default service), emit a single InstrumentationScope, and add service.name as a data-point attribute only when a span's service differs from the configured default. Thread DD_SERVICE through the processor so the transformer can compare against it. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): drop redundant dd-trace InstrumentationScope The exported OTLP metrics no longer carry an InstrumentationScope: a `dd-trace` scope (name/version) is redundant with the resource's telemetry.sdk.* attributes. The single scopeMetrics omits the scope field. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): datadog.* attribute prefix and OTEL_TRACES_SPAN_METRICS_ENABLED Rename the OTLP trace-metric attributes from dd.* to datadog.* (operation.name, span.type, span.top_level, origin, runtime_id, datadog.<process tags>) and rename the enablement env var OTEL_CLIENT_STATS_COMPUTATION_ENABLED -> OTEL_TRACES_SPAN_METRICS_ENABLED. Co-authored-by: Cursor <[email protected]> * feat(otlp): set _dd.stats_computed resource attribute on OTLP traces when trace metrics enabled Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): use timer.unref?.() for Electron compatibility Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(rebase): restore extractRootTags; rename traceMetricsEnabled; add otelSemanticsEnabled to types span_format.js: rebase conflict resolved to branch's addTag refactor which no longer exists in master — revert to explicit typeof checks while keeping the FR06.3 BUG comment. index.d.ts: rename traceMetricsEnabled -> otlpTraceMetricsEnabled to match supported-configurations.json; add otelSemanticsEnabled (DD_TRACE_OTEL_SEMANTICS_ENABLED). Fixes eslint-config-names-sync errors. Regenerate generated-config-types.d.ts from updated inputs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): wire OTLP metrics endpoint/protocol and trim dead code The crash fix: SpanStatsProcessor read config.otelMetricsUrl/otelMetricsProtocol, which Config never set (dropped in 40014ae), so `new URL(undefined)` threw ERR_INVALID_URL and crashed tracer init whenever OTLP trace metrics were enabled. Read the canonical OTEL_EXPORTER_OTLP_METRICS_ENDPOINT/OTEL_EXPORTER_OTLP_METRICS_PROTOCOL directly instead of introducing redundant alias properties. Also fix the dead auto-enable check: `this.otelMetricsEnabled` does not exist (the property is DD_METRICS_OTEL_ENABLED), so `undefined === true` made the "auto-enable when OTLP traces + OTEL metrics are on" path never trigger. Minimize the diff vs master without changing behavior: - drop 4 unused SpanAggStats fields (errorDuration/topLevel*) and their test - collapse the duplicate JSON/protobuf transformer methods into transform() - remove two `// BUG` WIP narration comments (reverts the comment-only span_format.js hunk; tracked separately) Co-Authored-By: Claude Opus 4.8 <[email protected]> Co-authored-by: Cursor <[email protected]> * refactor(span-stats): privatize internals, trim telemetry, simplify transformer - Make _drainBuckets / _toLegacyPayload true private (#) — neither crosses the class boundary; _ prefix implied false publicness - Guard SpanStatsExporter construction behind !otlpTraceMetricsEnabled so it is never instantiated when the OTLP path is active - Replace #errorStatus() / #boolAttr() one-shot methods with inline literals and a module-level ERROR_STATUS_ATTR constant to avoid per-call allocations - sketchToFixedHistogram now returns number[] directly; #pushPoint references EXPLICIT_BOUNDS_SECONDS from the module constant - Remove this.recordTelemetry calls from OtlpStatsExporter.export — not part of the OTLP trace-metrics spec - Rewrite whitebox _drainBuckets test as blackbox: assert buckets are empty after onInterval() instead of calling the private method Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix configs Co-authored-by: Munir Abdinur <[email protected]> * chore: regenerate config types after supported-configurations.json update Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): use otelSemanticsEnabled config key instead of DD_TRACE_OTEL_SEMANTICS_ENABLED Our branch maps DD_TRACE_OTEL_SEMANTICS_ENABLED to the internal property otelSemanticsEnabled via supported-configurations.json internalPropertyName. The merged master code was reading config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly, which was undefined in our config layout. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(test): update span.spec.js to use otelSemanticsEnabled config key The test was setting config.DD_TRACE_OTEL_SEMANTICS_ENABLED but span.js now reads config.otelSemanticsEnabled (the internal property name mapped from DD_TRACE_OTEL_SEMANTICS_ENABLED via supported-configurations.json). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use string default for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL Schema requires default to be a string or null, not a number literal. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): update description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL to match registry Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use short description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL The description field maps to Short Description in the config registry. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): remove description field from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL No other int entry with allowed field uses description; may be mutually exclusive in schema. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(otlp-span-stats): address Codex review comments - Use dd-trace VERSION (not app version) for telemetry.sdk.version resource attribute - Pass OTEL_EXPORTER_OTLP_METRICS_HEADERS and OTEL_EXPORTER_OTLP_METRICS_TIMEOUT to OtlpStatsExporter so authenticated/custom endpoints work correctly - Fix index.d.ts doc: env var is OTEL_TRACES_SPAN_METRICS_ENABLED and auto-enable condition is DD_METRICS_OTEL_ENABLED (not OTEL_METRICS_EXPORTER=otlp) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): fix import order and no-useless-undefined in span_stats and otlp-span-stats - Move ../../../version import before ./constants to satisfy import/order rule - Remove explicit = undefined default for headers param (unicorn/no-useless-undefined) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): cover _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL override branch Adds test that exercises the setAndTrack call inside the conditional that reads the internal flush interval override from the environment. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): align DD_TRACE_OTEL_SEMANTICS_ENABLED with registry definition The config registry has this entry as a plain boolean with default "false" and no internalPropertyName. Revert our custom mapping so the entry matches the registry exactly — the validator compares against the registered definition. All code that previously accessed config.otelSemanticsEnabled now reads config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly; the destructuring alias in span_stats.js preserves the otelSemanticsEnabled local variable name. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): add configurationNames for otelSemanticsEnabled to satisfy eslint-config-names-sync The rule requires that every option name in index.d.ts has a corresponding entry in supported-configurations.json (as a key, configurationNames value, or internalPropertyName). Adding configurationNames: ["otelSemanticsEnabled"] to DD_TRACE_OTEL_SEMANTICS_ENABLED satisfies this while keeping default: "false" to match the registry. The generator uses configurationNames[0] as the config key, so code reverts to config.otelSemanticsEnabled. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(config): remove internalPropertyName and unnecessary configurationNames Per reviewer feedback: - Remove internalPropertyName from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL; the flush interval is read directly via getEnvironmentVariable() in #applyCalculated - Remove configurationNames/internalPropertyName from OTEL_TRACES_SPAN_METRICS_ENABLED and drop otlpTraceMetricsEnabled as a programmatic option from index.d.ts; use this.OTEL_TRACES_SPAN_METRICS_ENABLED directly in #applyCalculated instead - Remove configurationNames from DD_TRACE_OTEL_SEMANTICS_ENABLED and drop otelSemanticsEnabled as a programmatic option from index.d.ts; all callers now read config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): resolve Unknown Config properties for otlpTraceMetricsEnabled/ddTraceMetricsOtelFlushInterval - Map setAndTrack to OTEL_TRACES_SPAN_METRICS_ENABLED (a declared config key) instead of the undeclared otlpTraceMetricsEnabled alias; all call sites updated to read config.OTEL_TRACES_SPAN_METRICS_ENABLED directly - Move _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL reading from #applyCalculated into span_stats.js; import getEnvironmentVariable there directly — removes the undeclared ddTraceMetricsOtelFlushInterval setAndTrack write - Update tests to use the env-var key names and remove the now-irrelevant config override test Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): remove blank line before closing brace in config spec Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(config): generate GeneratedEnvVarConfig interface for env var types - Extract getBaseType helper from getTypeForEntry to share base type computation - Add getEnvVarType that only adds undefined when there is no registered default - Add generateEnvVarConfigTypes to map every env var name (canonical + aliases) to its resolved type - Append GeneratedEnvVarConfig interface to generated-config-types.d.ts Rationale: Callers of getValueFromEnvSources need per-env-var typed return values instead of the full config property union, enabling type-safe lookups by literal env var name. This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): address PR review comments - Gate OTLP-only SpanAggKey dimensions (origin, spanKind, rpcMethod, rpcStatusCode) on otlpTraceMetricsEnabled to avoid inflating legacy span stats aggregation key cardinality - Thread _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL through the typed config system (via setAndTrack/getValueFromEnvSources) instead of reading the raw env var directly in SpanStatsProcessor - Add config tests covering OTEL_TRACES_SPAN_METRICS_ENABLED auto-enable logic (both conditions, explicit override) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): pass otlpEnabled=true in transformer test bucket helper makeBucket is used exclusively by OtlpStatsTransformer tests, so spans must be keyed with otlpEnabled=true to populate the OTLP-gated fields (origin, spanKind, rpcMethod, rpcStatusCode). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * style(span-stats): minor style and readability cleanups - Extract flush interval to variable before setAndTrack call - Remove unnecessary quotes on property key - Tighten test description wording Rationale: Small consistency and readability improvements from PR review This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): remove rpc.method from otlp span stats aggregation key - Drop grpc.method.name from SpanAggKey and OtlpStatsTransformer - rpc.method inflates aggregation key cardinality without sufficient benefit - Update all affected test assertions This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * refactor(span-stats): remove stale comments and clarify TODO - Remove redundant inline comments in otlp-span-stats transformer - Replace misleading comment about OTLP-only dimensions with a TODO noting origin and spanKind should eventually be included in legacy client stats aggregation This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * Apply suggestion from @mabdinur * refactor(span-stats): remove redundant inline comments - Drop comments that restate what the code already shows - Keep code self-documenting per project style guidelines This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: add otlp-span-stats exporter to CODEOWNERS Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): encapsulate OTLP span stats in opentelemetry/metrics Move OtlpStatsExporter and OtlpStatsTransformer into opentelemetry/metrics/ so the opentelemetry package is self-contained ahead of potential extraction into its own npm package. Key changes: - Move exporters/otlp-span-stats/{index,transformer}.js to opentelemetry/metrics/otlp_span_stats_{exporter,transformer}.js - Move buildResourceAttributes from span_stats.js to opentelemetry/metrics/index.js; add createOtlpSpanStatsExporter factory there - Wire OtlpStatsExporter via DI: opentracing/tracer.js creates it when OTEL_TRACES_SPAN_METRICS_ENABLED and passes it through SpanProcessor to SpanStatsProcessor — span_stats.js no longer imports from opentelemetry/ - config/index.js mirrors OTEL_TRACES_SPAN_METRICS_ENABLED into stats.DD_TRACE_STATS_COMPUTATION_ENABLED so downstream checks are unified - Remove otlpEnabled flag from SpanAggKey/SpanBuckets — origin, spanKind, rpcStatusCode are always populated - Remove OTEL-specific check from AgentExporter (relies on mirrored flag) - Remove CODEOWNERS entry for deleted exporters/otlp-span-stats/ path - Move tests to test/opentelemetry/metrics/ Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): align grpc stats with libdatadog and enforce mutual exclusion - Move GRPC_STATUS_CODE constant to ext/tags.js - Emit rpc.response.status_code as string (aligns with libdatadog kv_str) - Use else if in onInterval to make native and OTLP export mutually exclusive Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): trim JSDoc, privatize transformer, drop empty description - Make OtlpStatsExporter#transformer a private field (#transformer) - Remove empty description field from histogram metric - Trim redundant @param prose in exporter and transformer JSDoc - Use GRPC_STATUS_CODE import in transformer spec instead of string literal Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): suppress self-instrumentation spans from OTLP exporter requests Wrap sendPayload's HTTP request in legacyStorage.run({ noop: true }) so the tracer does not instrument its own outbound connections to the OTLP collector. Without this, tcp.connect client spans for /v1/metrics requests were fed into the traces.span.sdk.metrics.duration histogram, displacing real span data points and inflating counts in the system-tests parametric suite. Same pattern used by exporters/common/request.js and exporters/common/agents.js. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(opentelemetry): improve patch coverage for span stats OTLP export - Inline URL parsing into OtlpHttpExporterBase constructor; remove setUrl (the if(telemetryTags !== undefined) branch was dead — telemetryTags is always undefined when setUrl was called from the constructor, and no external caller ever invoked it post-construction) - Add tests for buildResourceAttributes (sdk identity, runtime-id, OTel- semantics mode) and createOtlpSpanStatsExporter - Add tests for HTTP error response and request error paths in sendPayload Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(opentelemetry): emit raw grpc.status.code name for rpc.response.status_code Prefer the meta status NAME string over the numeric metrics tag and emit it upper-cased to rpc.response.status_code, aligning with the OTel gRPC semantic conventions (canonical status name) without any code<->name mapping. * fix(opentelemetry): restore setUrl method removed by dead-code cleanup Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): trim redundant comments in grpc status mapping Co-authored-by: Cursor <[email protected]> * refactor(opentelemetry): read grpc.status.code from meta only Drop the numeric metrics fallback; the gRPC status code is the canonical status NAME and is read from span meta. Co-authored-by: Cursor <[email protected]> * fix(opentelemetry): translate numeric grpc.status.code from metrics to status name The dd gRPC plugin sets grpc.status.code as a numeric integer via span.setTag, which span_format.js routes into span.metrics rather than span.meta. SpanAggKey was reading meta only, so rpcStatusCode was always empty for real gRPC spans. Now falls back to span.metrics[GRPC_STATUS_CODE] and translates the integer to the canonical status name (OK, NOT_FOUND, etc.) using the gRPC status code table. Meta string takes priority when both are present. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): split agg key by top-level to fix mixed-bucket OTLP metrics When a bucket contained both top-level and measured non-top-level spans, the heuristic (topLevelHits === hits) always resolved to false, causing the OTLP histogram to be emitted as datadog.span.top_level=false and dropping top-level traffic from APM metrics. Adding topLevel as a dimension to SpanAggKey causes top-level and non-top-level spans to bucket separately. Each bucket is now always purely top-level or purely non-top-level, so the attribute is always accurate. The native stats path is unaffected because toJSON() omits topLevel; the Agent merges groups with identical key fields, preserving the same Hits/TopLevelHits totals. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): track per-top-level distributions to fix native stats regression The previous fix added topLevel to SpanAggKey to separate top-level and non-top-level spans into distinct buckets. This created a real regression in the native /v0.6/stats path: the Agent's mergeDuplicates() correctly sums Hits/Errors/Duration from duplicate rows but silently drops TopLevelHits from the merged-away entry. If the non-top-level row is processed first and becomes the canonical, TopLevelHits from the top-level row is lost. Fix: revert topLevel from SpanAggKey (no more duplicate rows). Instead, split SpanAggStats into four distributions (topLevelOk, topLevelError, nonTopLevelOk, nonTopLevelError). The native stats path merges them at export time so toJSON() produces the same combined OkSummary/ErrorSummary as before. The OTLP path emits separate data points per top-level status with the correct datadog.span.top_level attribute. OTel-semantics mode merges the distributions (no top-level attribute to distinguish them). Also adds SpanKind, Origin, and RpcStatusCode to the native stats toJSON() payload so the Agent receives these new dimensions. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): split native stats rows by top-level status; use GRPCStatusCode key toJSON() now returns an array of up to 2 rows (top-level row first, non-top-level row second). #toLegacyPayload uses flatMap to flatten them. This eliminates the merge-time DDSketch allocation and ensures TopLevelHits is always non-zero on the top-level row, so the Agent's mergeDuplicates retains it as the canonical entry. Duration and Errors are derived from distribution .sum/.count, removing the redundant this.duration and this.errors accumulators. GRPCStatusCode matches the agent's msgpack decoder key (confirmed from pkg/proto/pbgo/trace/stats_gen.go). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): minimize comments, move GRPC_STATUS_NAMES to constants - Remove descriptive/narrating comments throughout; keep only non-obvious constraints - Move GRPC_STATUS_NAMES from span_stats.js into constants.js - Rename #toLegacyPayload -> #v06Payload - Remove section comment from ext/tags.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): restrict ORIGIN_KEY to synthetics boolean; remove origin from aggregation key ORIGIN_KEY now only populates SpanAggKey.synthetics. The origin string field is removed from SpanAggKey, toString(), and the v0.6 payload (Origin is not a field the agent decodes). In the OTLP path, datadog.origin='synthetics' is emitted when aggKey.synthetics is true. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: remove pr_description.md --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Munir <[email protected]> Co-authored-by: Cursor <[email protected]>
* feat(span-stats): add OTLP metrics export for span stats Export client-computed span stats as OTLP metrics (dd.trace.span.hits, dd.trace.span.errors, dd.trace.span.top_level_hits, dd.trace.span.duration) via a new OtlpStatsExporter alongside the existing Datadog /v0.6/stats exporter. Enabled via DD_TRACE_OTEL_METRICS_ENABLED=true, or auto-enabled when both OTEL_TRACES_EXPORTER=otlp and OTEL_METRICS_EXPORTER=otlp are set. URL and protocol are derived from the OTLP trace export configuration. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): register traceMetrics as internal runtime property traceMetrics is a computed aggregate derived from OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER, and DD_TRACE_OTEL_METRICS_ENABLED — not a raw user-facing key — so it belongs in INTERNAL_RUNTIME_PROPERTIES alongside sampler and stableConfig. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): guard traceMetrics URL derivation against invalid OTLP endpoint When hostname is an unbracketed IPv6 address (e.g. ::1), the defaultOtlpBase is http://::1:4318 which is not a valid URL. The new URL() call in the traceMetrics block was the first code path to actually parse the string, causing a TypeError that crashed config construction. Wrap the URL derivation in a try/catch so that a malformed traces endpoint falls back to the localhost default without throwing. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(config): regenerate config types for DD_TRACE_OTEL_METRICS_ENABLED Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): add configurationNames to DD_TRACE_OTEL_METRICS_ENABLED entry The eslint-config-names-sync rule verifies that every leaf property in TracerOptions (index.d.ts) has a matching configurationNames entry in supported-configurations.json. The entry for DD_TRACE_OTEL_METRICS_ENABLED only had internalPropertyName, which is not checked by the rule. Adding configurationNames: ["traceMetricsEnabled"] ties the two files together and satisfies the lint check. Regenerated config types to match. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * bug fix * update post RFC discussion * bring implementation bug fix * clean how cinfgs are set * feat(otlp-trace-metrics): align span-stats export with the trace-metrics contract Update the OTLP trace-metrics export to match the agreed RFC/system-test contract: - Rename the enablement env var to OTEL_CLIENT_STATS_COMPUTATION_ENABLED and add DD_TRACE_OTEL_SEMANTICS_ENABLED (OTel-semantics mode: emit only OTel attributes, no dd.*). - Emit a single histogram named traces.span.sdk.metrics.duration. - Map dimensions to OTel attributes (span.name, span.kind, http.*, rpc.* from grpc tags) and convey errors via OTel status.code; default mode also adds dd.operation.name, dd.span.type, dd.origin and dd.span.top_level. - Add telemetry.sdk.{name,language,version} resource attributes and emit process tags as dd.<key> (default mode only); gate all dd.* resource attributes behind default mode. - Drive the flush/export cadence from OTEL_METRIC_EXPORT_INTERVAL and drop the _DD_TRACE_STATS_WRITER_INTERVAL override. - Read grpc.status.code from span.metrics (numeric) with a meta fallback. Update unit tests accordingly and regenerate config types. Co-authored-by: Cursor <[email protected]> * feat(otlp-trace-metrics): report service identity per InstrumentationScope Partition span-stats data points by service so one OTLP payload can carry multiple services, each as its own InstrumentationScope with service.name, service.version and deployment.environment.name. These move off the resource, which now only carries SDK identity, host.name and dd.* attributes. Fix the trace-metrics flush cadence at 10s (no longer driven by OTEL_METRIC_EXPORT_INTERVAL); the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL overrides it in tests only. Co-authored-by: Cursor <[email protected]> * fix(otlp-trace-metrics): apply internal flush interval override The generic env applier only reads DD_/OTEL_ prefixed vars, so the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (which starts with _DD_) was never wired into config. Read it explicitly so the test-only flush cadence override takes effect. Co-authored-by: Cursor <[email protected]> * feat(otlp-span-stats): emit fixed explicit-bounds histogram from the DDSketch Derive the OTLP duration histogram from each group's DDSketch into the spanmetrics-connector default bounds (in seconds), and drop the duplicate exact-cell accumulator in span_stats. Each group now emits at most two data points (ok/error) with a per-group dd.span.top_level heuristic, mirroring libdatadog. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): carry service identity as resource attributes Move service.name/service.version/deployment.environment.name onto the OTLP resource (the configured default service), emit a single InstrumentationScope, and add service.name as a data-point attribute only when a span's service differs from the configured default. Thread DD_SERVICE through the processor so the transformer can compare against it. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): drop redundant dd-trace InstrumentationScope The exported OTLP metrics no longer carry an InstrumentationScope: a `dd-trace` scope (name/version) is redundant with the resource's telemetry.sdk.* attributes. The single scopeMetrics omits the scope field. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): datadog.* attribute prefix and OTEL_TRACES_SPAN_METRICS_ENABLED Rename the OTLP trace-metric attributes from dd.* to datadog.* (operation.name, span.type, span.top_level, origin, runtime_id, datadog.<process tags>) and rename the enablement env var OTEL_CLIENT_STATS_COMPUTATION_ENABLED -> OTEL_TRACES_SPAN_METRICS_ENABLED. Co-authored-by: Cursor <[email protected]> * feat(otlp): set _dd.stats_computed resource attribute on OTLP traces when trace metrics enabled Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): use timer.unref?.() for Electron compatibility Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(rebase): restore extractRootTags; rename traceMetricsEnabled; add otelSemanticsEnabled to types span_format.js: rebase conflict resolved to branch's addTag refactor which no longer exists in master — revert to explicit typeof checks while keeping the FR06.3 BUG comment. index.d.ts: rename traceMetricsEnabled -> otlpTraceMetricsEnabled to match supported-configurations.json; add otelSemanticsEnabled (DD_TRACE_OTEL_SEMANTICS_ENABLED). Fixes eslint-config-names-sync errors. Regenerate generated-config-types.d.ts from updated inputs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): wire OTLP metrics endpoint/protocol and trim dead code The crash fix: SpanStatsProcessor read config.otelMetricsUrl/otelMetricsProtocol, which Config never set (dropped in 40014ae), so `new URL(undefined)` threw ERR_INVALID_URL and crashed tracer init whenever OTLP trace metrics were enabled. Read the canonical OTEL_EXPORTER_OTLP_METRICS_ENDPOINT/OTEL_EXPORTER_OTLP_METRICS_PROTOCOL directly instead of introducing redundant alias properties. Also fix the dead auto-enable check: `this.otelMetricsEnabled` does not exist (the property is DD_METRICS_OTEL_ENABLED), so `undefined === true` made the "auto-enable when OTLP traces + OTEL metrics are on" path never trigger. Minimize the diff vs master without changing behavior: - drop 4 unused SpanAggStats fields (errorDuration/topLevel*) and their test - collapse the duplicate JSON/protobuf transformer methods into transform() - remove two `// BUG` WIP narration comments (reverts the comment-only span_format.js hunk; tracked separately) Co-Authored-By: Claude Opus 4.8 <[email protected]> Co-authored-by: Cursor <[email protected]> * refactor(span-stats): privatize internals, trim telemetry, simplify transformer - Make _drainBuckets / _toLegacyPayload true private (#) — neither crosses the class boundary; _ prefix implied false publicness - Guard SpanStatsExporter construction behind !otlpTraceMetricsEnabled so it is never instantiated when the OTLP path is active - Replace #errorStatus() / #boolAttr() one-shot methods with inline literals and a module-level ERROR_STATUS_ATTR constant to avoid per-call allocations - sketchToFixedHistogram now returns number[] directly; #pushPoint references EXPLICIT_BOUNDS_SECONDS from the module constant - Remove this.recordTelemetry calls from OtlpStatsExporter.export — not part of the OTLP trace-metrics spec - Rewrite whitebox _drainBuckets test as blackbox: assert buckets are empty after onInterval() instead of calling the private method Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix configs Co-authored-by: Munir Abdinur <[email protected]> * chore: regenerate config types after supported-configurations.json update Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): use otelSemanticsEnabled config key instead of DD_TRACE_OTEL_SEMANTICS_ENABLED Our branch maps DD_TRACE_OTEL_SEMANTICS_ENABLED to the internal property otelSemanticsEnabled via supported-configurations.json internalPropertyName. The merged master code was reading config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly, which was undefined in our config layout. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(test): update span.spec.js to use otelSemanticsEnabled config key The test was setting config.DD_TRACE_OTEL_SEMANTICS_ENABLED but span.js now reads config.otelSemanticsEnabled (the internal property name mapped from DD_TRACE_OTEL_SEMANTICS_ENABLED via supported-configurations.json). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use string default for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL Schema requires default to be a string or null, not a number literal. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): update description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL to match registry Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use short description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL The description field maps to Short Description in the config registry. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): remove description field from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL No other int entry with allowed field uses description; may be mutually exclusive in schema. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(otlp-span-stats): address Codex review comments - Use dd-trace VERSION (not app version) for telemetry.sdk.version resource attribute - Pass OTEL_EXPORTER_OTLP_METRICS_HEADERS and OTEL_EXPORTER_OTLP_METRICS_TIMEOUT to OtlpStatsExporter so authenticated/custom endpoints work correctly - Fix index.d.ts doc: env var is OTEL_TRACES_SPAN_METRICS_ENABLED and auto-enable condition is DD_METRICS_OTEL_ENABLED (not OTEL_METRICS_EXPORTER=otlp) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): fix import order and no-useless-undefined in span_stats and otlp-span-stats - Move ../../../version import before ./constants to satisfy import/order rule - Remove explicit = undefined default for headers param (unicorn/no-useless-undefined) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): cover _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL override branch Adds test that exercises the setAndTrack call inside the conditional that reads the internal flush interval override from the environment. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): align DD_TRACE_OTEL_SEMANTICS_ENABLED with registry definition The config registry has this entry as a plain boolean with default "false" and no internalPropertyName. Revert our custom mapping so the entry matches the registry exactly — the validator compares against the registered definition. All code that previously accessed config.otelSemanticsEnabled now reads config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly; the destructuring alias in span_stats.js preserves the otelSemanticsEnabled local variable name. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): add configurationNames for otelSemanticsEnabled to satisfy eslint-config-names-sync The rule requires that every option name in index.d.ts has a corresponding entry in supported-configurations.json (as a key, configurationNames value, or internalPropertyName). Adding configurationNames: ["otelSemanticsEnabled"] to DD_TRACE_OTEL_SEMANTICS_ENABLED satisfies this while keeping default: "false" to match the registry. The generator uses configurationNames[0] as the config key, so code reverts to config.otelSemanticsEnabled. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(config): remove internalPropertyName and unnecessary configurationNames Per reviewer feedback: - Remove internalPropertyName from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL; the flush interval is read directly via getEnvironmentVariable() in #applyCalculated - Remove configurationNames/internalPropertyName from OTEL_TRACES_SPAN_METRICS_ENABLED and drop otlpTraceMetricsEnabled as a programmatic option from index.d.ts; use this.OTEL_TRACES_SPAN_METRICS_ENABLED directly in #applyCalculated instead - Remove configurationNames from DD_TRACE_OTEL_SEMANTICS_ENABLED and drop otelSemanticsEnabled as a programmatic option from index.d.ts; all callers now read config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): resolve Unknown Config properties for otlpTraceMetricsEnabled/ddTraceMetricsOtelFlushInterval - Map setAndTrack to OTEL_TRACES_SPAN_METRICS_ENABLED (a declared config key) instead of the undeclared otlpTraceMetricsEnabled alias; all call sites updated to read config.OTEL_TRACES_SPAN_METRICS_ENABLED directly - Move _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL reading from #applyCalculated into span_stats.js; import getEnvironmentVariable there directly — removes the undeclared ddTraceMetricsOtelFlushInterval setAndTrack write - Update tests to use the env-var key names and remove the now-irrelevant config override test Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): remove blank line before closing brace in config spec Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(config): generate GeneratedEnvVarConfig interface for env var types - Extract getBaseType helper from getTypeForEntry to share base type computation - Add getEnvVarType that only adds undefined when there is no registered default - Add generateEnvVarConfigTypes to map every env var name (canonical + aliases) to its resolved type - Append GeneratedEnvVarConfig interface to generated-config-types.d.ts Rationale: Callers of getValueFromEnvSources need per-env-var typed return values instead of the full config property union, enabling type-safe lookups by literal env var name. This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): address PR review comments - Gate OTLP-only SpanAggKey dimensions (origin, spanKind, rpcMethod, rpcStatusCode) on otlpTraceMetricsEnabled to avoid inflating legacy span stats aggregation key cardinality - Thread _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL through the typed config system (via setAndTrack/getValueFromEnvSources) instead of reading the raw env var directly in SpanStatsProcessor - Add config tests covering OTEL_TRACES_SPAN_METRICS_ENABLED auto-enable logic (both conditions, explicit override) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): pass otlpEnabled=true in transformer test bucket helper makeBucket is used exclusively by OtlpStatsTransformer tests, so spans must be keyed with otlpEnabled=true to populate the OTLP-gated fields (origin, spanKind, rpcMethod, rpcStatusCode). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * style(span-stats): minor style and readability cleanups - Extract flush interval to variable before setAndTrack call - Remove unnecessary quotes on property key - Tighten test description wording Rationale: Small consistency and readability improvements from PR review This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): remove rpc.method from otlp span stats aggregation key - Drop grpc.method.name from SpanAggKey and OtlpStatsTransformer - rpc.method inflates aggregation key cardinality without sufficient benefit - Update all affected test assertions This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * refactor(span-stats): remove stale comments and clarify TODO - Remove redundant inline comments in otlp-span-stats transformer - Replace misleading comment about OTLP-only dimensions with a TODO noting origin and spanKind should eventually be included in legacy client stats aggregation This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * Apply suggestion from @mabdinur * refactor(span-stats): remove redundant inline comments - Drop comments that restate what the code already shows - Keep code self-documenting per project style guidelines This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: add otlp-span-stats exporter to CODEOWNERS Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): encapsulate OTLP span stats in opentelemetry/metrics Move OtlpStatsExporter and OtlpStatsTransformer into opentelemetry/metrics/ so the opentelemetry package is self-contained ahead of potential extraction into its own npm package. Key changes: - Move exporters/otlp-span-stats/{index,transformer}.js to opentelemetry/metrics/otlp_span_stats_{exporter,transformer}.js - Move buildResourceAttributes from span_stats.js to opentelemetry/metrics/index.js; add createOtlpSpanStatsExporter factory there - Wire OtlpStatsExporter via DI: opentracing/tracer.js creates it when OTEL_TRACES_SPAN_METRICS_ENABLED and passes it through SpanProcessor to SpanStatsProcessor — span_stats.js no longer imports from opentelemetry/ - config/index.js mirrors OTEL_TRACES_SPAN_METRICS_ENABLED into stats.DD_TRACE_STATS_COMPUTATION_ENABLED so downstream checks are unified - Remove otlpEnabled flag from SpanAggKey/SpanBuckets — origin, spanKind, rpcStatusCode are always populated - Remove OTEL-specific check from AgentExporter (relies on mirrored flag) - Remove CODEOWNERS entry for deleted exporters/otlp-span-stats/ path - Move tests to test/opentelemetry/metrics/ Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): align grpc stats with libdatadog and enforce mutual exclusion - Move GRPC_STATUS_CODE constant to ext/tags.js - Emit rpc.response.status_code as string (aligns with libdatadog kv_str) - Use else if in onInterval to make native and OTLP export mutually exclusive Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): trim JSDoc, privatize transformer, drop empty description - Make OtlpStatsExporter#transformer a private field (#transformer) - Remove empty description field from histogram metric - Trim redundant @param prose in exporter and transformer JSDoc - Use GRPC_STATUS_CODE import in transformer spec instead of string literal Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): suppress self-instrumentation spans from OTLP exporter requests Wrap sendPayload's HTTP request in legacyStorage.run({ noop: true }) so the tracer does not instrument its own outbound connections to the OTLP collector. Without this, tcp.connect client spans for /v1/metrics requests were fed into the traces.span.sdk.metrics.duration histogram, displacing real span data points and inflating counts in the system-tests parametric suite. Same pattern used by exporters/common/request.js and exporters/common/agents.js. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(opentelemetry): improve patch coverage for span stats OTLP export - Inline URL parsing into OtlpHttpExporterBase constructor; remove setUrl (the if(telemetryTags !== undefined) branch was dead — telemetryTags is always undefined when setUrl was called from the constructor, and no external caller ever invoked it post-construction) - Add tests for buildResourceAttributes (sdk identity, runtime-id, OTel- semantics mode) and createOtlpSpanStatsExporter - Add tests for HTTP error response and request error paths in sendPayload Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(opentelemetry): emit raw grpc.status.code name for rpc.response.status_code Prefer the meta status NAME string over the numeric metrics tag and emit it upper-cased to rpc.response.status_code, aligning with the OTel gRPC semantic conventions (canonical status name) without any code<->name mapping. * fix(opentelemetry): restore setUrl method removed by dead-code cleanup Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): trim redundant comments in grpc status mapping Co-authored-by: Cursor <[email protected]> * refactor(opentelemetry): read grpc.status.code from meta only Drop the numeric metrics fallback; the gRPC status code is the canonical status NAME and is read from span meta. Co-authored-by: Cursor <[email protected]> * fix(opentelemetry): translate numeric grpc.status.code from metrics to status name The dd gRPC plugin sets grpc.status.code as a numeric integer via span.setTag, which span_format.js routes into span.metrics rather than span.meta. SpanAggKey was reading meta only, so rpcStatusCode was always empty for real gRPC spans. Now falls back to span.metrics[GRPC_STATUS_CODE] and translates the integer to the canonical status name (OK, NOT_FOUND, etc.) using the gRPC status code table. Meta string takes priority when both are present. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): split agg key by top-level to fix mixed-bucket OTLP metrics When a bucket contained both top-level and measured non-top-level spans, the heuristic (topLevelHits === hits) always resolved to false, causing the OTLP histogram to be emitted as datadog.span.top_level=false and dropping top-level traffic from APM metrics. Adding topLevel as a dimension to SpanAggKey causes top-level and non-top-level spans to bucket separately. Each bucket is now always purely top-level or purely non-top-level, so the attribute is always accurate. The native stats path is unaffected because toJSON() omits topLevel; the Agent merges groups with identical key fields, preserving the same Hits/TopLevelHits totals. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): track per-top-level distributions to fix native stats regression The previous fix added topLevel to SpanAggKey to separate top-level and non-top-level spans into distinct buckets. This created a real regression in the native /v0.6/stats path: the Agent's mergeDuplicates() correctly sums Hits/Errors/Duration from duplicate rows but silently drops TopLevelHits from the merged-away entry. If the non-top-level row is processed first and becomes the canonical, TopLevelHits from the top-level row is lost. Fix: revert topLevel from SpanAggKey (no more duplicate rows). Instead, split SpanAggStats into four distributions (topLevelOk, topLevelError, nonTopLevelOk, nonTopLevelError). The native stats path merges them at export time so toJSON() produces the same combined OkSummary/ErrorSummary as before. The OTLP path emits separate data points per top-level status with the correct datadog.span.top_level attribute. OTel-semantics mode merges the distributions (no top-level attribute to distinguish them). Also adds SpanKind, Origin, and RpcStatusCode to the native stats toJSON() payload so the Agent receives these new dimensions. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): split native stats rows by top-level status; use GRPCStatusCode key toJSON() now returns an array of up to 2 rows (top-level row first, non-top-level row second). #toLegacyPayload uses flatMap to flatten them. This eliminates the merge-time DDSketch allocation and ensures TopLevelHits is always non-zero on the top-level row, so the Agent's mergeDuplicates retains it as the canonical entry. Duration and Errors are derived from distribution .sum/.count, removing the redundant this.duration and this.errors accumulators. GRPCStatusCode matches the agent's msgpack decoder key (confirmed from pkg/proto/pbgo/trace/stats_gen.go). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): minimize comments, move GRPC_STATUS_NAMES to constants - Remove descriptive/narrating comments throughout; keep only non-obvious constraints - Move GRPC_STATUS_NAMES from span_stats.js into constants.js - Rename #toLegacyPayload -> #v06Payload - Remove section comment from ext/tags.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): restrict ORIGIN_KEY to synthetics boolean; remove origin from aggregation key ORIGIN_KEY now only populates SpanAggKey.synthetics. The origin string field is removed from SpanAggKey, toString(), and the v0.6 payload (Origin is not a field the agent decodes). In the OTLP path, datadog.origin='synthetics' is emitted when aggKey.synthetics is true. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: remove pr_description.md --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Munir <[email protected]> Co-authored-by: Cursor <[email protected]>
* feat(span-stats): add OTLP metrics export for span stats Export client-computed span stats as OTLP metrics (dd.trace.span.hits, dd.trace.span.errors, dd.trace.span.top_level_hits, dd.trace.span.duration) via a new OtlpStatsExporter alongside the existing Datadog /v0.6/stats exporter. Enabled via DD_TRACE_OTEL_METRICS_ENABLED=true, or auto-enabled when both OTEL_TRACES_EXPORTER=otlp and OTEL_METRICS_EXPORTER=otlp are set. URL and protocol are derived from the OTLP trace export configuration. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): register traceMetrics as internal runtime property traceMetrics is a computed aggregate derived from OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER, and DD_TRACE_OTEL_METRICS_ENABLED — not a raw user-facing key — so it belongs in INTERNAL_RUNTIME_PROPERTIES alongside sampler and stableConfig. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): guard traceMetrics URL derivation against invalid OTLP endpoint When hostname is an unbracketed IPv6 address (e.g. ::1), the defaultOtlpBase is http://::1:4318 which is not a valid URL. The new URL() call in the traceMetrics block was the first code path to actually parse the string, causing a TypeError that crashed config construction. Wrap the URL derivation in a try/catch so that a malformed traces endpoint falls back to the localhost default without throwing. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(config): regenerate config types for DD_TRACE_OTEL_METRICS_ENABLED Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): add configurationNames to DD_TRACE_OTEL_METRICS_ENABLED entry The eslint-config-names-sync rule verifies that every leaf property in TracerOptions (index.d.ts) has a matching configurationNames entry in supported-configurations.json. The entry for DD_TRACE_OTEL_METRICS_ENABLED only had internalPropertyName, which is not checked by the rule. Adding configurationNames: ["traceMetricsEnabled"] ties the two files together and satisfies the lint check. Regenerated config types to match. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * bug fix * update post RFC discussion * bring implementation bug fix * clean how cinfgs are set * feat(otlp-trace-metrics): align span-stats export with the trace-metrics contract Update the OTLP trace-metrics export to match the agreed RFC/system-test contract: - Rename the enablement env var to OTEL_CLIENT_STATS_COMPUTATION_ENABLED and add DD_TRACE_OTEL_SEMANTICS_ENABLED (OTel-semantics mode: emit only OTel attributes, no dd.*). - Emit a single histogram named traces.span.sdk.metrics.duration. - Map dimensions to OTel attributes (span.name, span.kind, http.*, rpc.* from grpc tags) and convey errors via OTel status.code; default mode also adds dd.operation.name, dd.span.type, dd.origin and dd.span.top_level. - Add telemetry.sdk.{name,language,version} resource attributes and emit process tags as dd.<key> (default mode only); gate all dd.* resource attributes behind default mode. - Drive the flush/export cadence from OTEL_METRIC_EXPORT_INTERVAL and drop the _DD_TRACE_STATS_WRITER_INTERVAL override. - Read grpc.status.code from span.metrics (numeric) with a meta fallback. Update unit tests accordingly and regenerate config types. Co-authored-by: Cursor <[email protected]> * feat(otlp-trace-metrics): report service identity per InstrumentationScope Partition span-stats data points by service so one OTLP payload can carry multiple services, each as its own InstrumentationScope with service.name, service.version and deployment.environment.name. These move off the resource, which now only carries SDK identity, host.name and dd.* attributes. Fix the trace-metrics flush cadence at 10s (no longer driven by OTEL_METRIC_EXPORT_INTERVAL); the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL overrides it in tests only. Co-authored-by: Cursor <[email protected]> * fix(otlp-trace-metrics): apply internal flush interval override The generic env applier only reads DD_/OTEL_ prefixed vars, so the internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (which starts with _DD_) was never wired into config. Read it explicitly so the test-only flush cadence override takes effect. Co-authored-by: Cursor <[email protected]> * feat(otlp-span-stats): emit fixed explicit-bounds histogram from the DDSketch Derive the OTLP duration histogram from each group's DDSketch into the spanmetrics-connector default bounds (in seconds), and drop the duplicate exact-cell accumulator in span_stats. Each group now emits at most two data points (ok/error) with a per-group dd.span.top_level heuristic, mirroring libdatadog. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): carry service identity as resource attributes Move service.name/service.version/deployment.environment.name onto the OTLP resource (the configured default service), emit a single InstrumentationScope, and add service.name as a data-point attribute only when a span's service differs from the configured default. Thread DD_SERVICE through the processor so the transformer can compare against it. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): drop redundant dd-trace InstrumentationScope The exported OTLP metrics no longer carry an InstrumentationScope: a `dd-trace` scope (name/version) is redundant with the resource's telemetry.sdk.* attributes. The single scopeMetrics omits the scope field. Co-authored-by: Cursor <[email protected]> * refactor(span-stats): datadog.* attribute prefix and OTEL_TRACES_SPAN_METRICS_ENABLED Rename the OTLP trace-metric attributes from dd.* to datadog.* (operation.name, span.type, span.top_level, origin, runtime_id, datadog.<process tags>) and rename the enablement env var OTEL_CLIENT_STATS_COMPUTATION_ENABLED -> OTEL_TRACES_SPAN_METRICS_ENABLED. Co-authored-by: Cursor <[email protected]> * feat(otlp): set _dd.stats_computed resource attribute on OTLP traces when trace metrics enabled Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): use timer.unref?.() for Electron compatibility Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(rebase): restore extractRootTags; rename traceMetricsEnabled; add otelSemanticsEnabled to types span_format.js: rebase conflict resolved to branch's addTag refactor which no longer exists in master — revert to explicit typeof checks while keeping the FR06.3 BUG comment. index.d.ts: rename traceMetricsEnabled -> otlpTraceMetricsEnabled to match supported-configurations.json; add otelSemanticsEnabled (DD_TRACE_OTEL_SEMANTICS_ENABLED). Fixes eslint-config-names-sync errors. Regenerate generated-config-types.d.ts from updated inputs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): wire OTLP metrics endpoint/protocol and trim dead code The crash fix: SpanStatsProcessor read config.otelMetricsUrl/otelMetricsProtocol, which Config never set (dropped in 40014ae), so `new URL(undefined)` threw ERR_INVALID_URL and crashed tracer init whenever OTLP trace metrics were enabled. Read the canonical OTEL_EXPORTER_OTLP_METRICS_ENDPOINT/OTEL_EXPORTER_OTLP_METRICS_PROTOCOL directly instead of introducing redundant alias properties. Also fix the dead auto-enable check: `this.otelMetricsEnabled` does not exist (the property is DD_METRICS_OTEL_ENABLED), so `undefined === true` made the "auto-enable when OTLP traces + OTEL metrics are on" path never trigger. Minimize the diff vs master without changing behavior: - drop 4 unused SpanAggStats fields (errorDuration/topLevel*) and their test - collapse the duplicate JSON/protobuf transformer methods into transform() - remove two `// BUG` WIP narration comments (reverts the comment-only span_format.js hunk; tracked separately) Co-Authored-By: Claude Opus 4.8 <[email protected]> Co-authored-by: Cursor <[email protected]> * refactor(span-stats): privatize internals, trim telemetry, simplify transformer - Make _drainBuckets / _toLegacyPayload true private (#) — neither crosses the class boundary; _ prefix implied false publicness - Guard SpanStatsExporter construction behind !otlpTraceMetricsEnabled so it is never instantiated when the OTLP path is active - Replace #errorStatus() / #boolAttr() one-shot methods with inline literals and a module-level ERROR_STATUS_ATTR constant to avoid per-call allocations - sketchToFixedHistogram now returns number[] directly; #pushPoint references EXPLICIT_BOUNDS_SECONDS from the module constant - Remove this.recordTelemetry calls from OtlpStatsExporter.export — not part of the OTLP trace-metrics spec - Rewrite whitebox _drainBuckets test as blackbox: assert buckets are empty after onInterval() instead of calling the private method Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix configs Co-authored-by: Munir Abdinur <[email protected]> * chore: regenerate config types after supported-configurations.json update Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): use otelSemanticsEnabled config key instead of DD_TRACE_OTEL_SEMANTICS_ENABLED Our branch maps DD_TRACE_OTEL_SEMANTICS_ENABLED to the internal property otelSemanticsEnabled via supported-configurations.json internalPropertyName. The merged master code was reading config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly, which was undefined in our config layout. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(test): update span.spec.js to use otelSemanticsEnabled config key The test was setting config.DD_TRACE_OTEL_SEMANTICS_ENABLED but span.js now reads config.otelSemanticsEnabled (the internal property name mapped from DD_TRACE_OTEL_SEMANTICS_ENABLED via supported-configurations.json). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use string default for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL Schema requires default to be a string or null, not a number literal. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): update description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL to match registry Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): use short description for _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL The description field maps to Short Description in the config registry. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): remove description field from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL No other int entry with allowed field uses description; may be mutually exclusive in schema. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(otlp-span-stats): address Codex review comments - Use dd-trace VERSION (not app version) for telemetry.sdk.version resource attribute - Pass OTEL_EXPORTER_OTLP_METRICS_HEADERS and OTEL_EXPORTER_OTLP_METRICS_TIMEOUT to OtlpStatsExporter so authenticated/custom endpoints work correctly - Fix index.d.ts doc: env var is OTEL_TRACES_SPAN_METRICS_ENABLED and auto-enable condition is DD_METRICS_OTEL_ENABLED (not OTEL_METRICS_EXPORTER=otlp) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): fix import order and no-useless-undefined in span_stats and otlp-span-stats - Move ../../../version import before ./constants to satisfy import/order rule - Remove explicit = undefined default for headers param (unicorn/no-useless-undefined) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(config): cover _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL override branch Adds test that exercises the setAndTrack call inside the conditional that reads the internal flush interval override from the environment. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): align DD_TRACE_OTEL_SEMANTICS_ENABLED with registry definition The config registry has this entry as a plain boolean with default "false" and no internalPropertyName. Revert our custom mapping so the entry matches the registry exactly — the validator compares against the registered definition. All code that previously accessed config.otelSemanticsEnabled now reads config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly; the destructuring alias in span_stats.js preserves the otelSemanticsEnabled local variable name. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): add configurationNames for otelSemanticsEnabled to satisfy eslint-config-names-sync The rule requires that every option name in index.d.ts has a corresponding entry in supported-configurations.json (as a key, configurationNames value, or internalPropertyName). Adding configurationNames: ["otelSemanticsEnabled"] to DD_TRACE_OTEL_SEMANTICS_ENABLED satisfies this while keeping default: "false" to match the registry. The generator uses configurationNames[0] as the config key, so code reverts to config.otelSemanticsEnabled. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(config): remove internalPropertyName and unnecessary configurationNames Per reviewer feedback: - Remove internalPropertyName from _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL; the flush interval is read directly via getEnvironmentVariable() in #applyCalculated - Remove configurationNames/internalPropertyName from OTEL_TRACES_SPAN_METRICS_ENABLED and drop otlpTraceMetricsEnabled as a programmatic option from index.d.ts; use this.OTEL_TRACES_SPAN_METRICS_ENABLED directly in #applyCalculated instead - Remove configurationNames from DD_TRACE_OTEL_SEMANTICS_ENABLED and drop otelSemanticsEnabled as a programmatic option from index.d.ts; all callers now read config.DD_TRACE_OTEL_SEMANTICS_ENABLED directly Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(config): resolve Unknown Config properties for otlpTraceMetricsEnabled/ddTraceMetricsOtelFlushInterval - Map setAndTrack to OTEL_TRACES_SPAN_METRICS_ENABLED (a declared config key) instead of the undeclared otlpTraceMetricsEnabled alias; all call sites updated to read config.OTEL_TRACES_SPAN_METRICS_ENABLED directly - Move _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL reading from #applyCalculated into span_stats.js; import getEnvironmentVariable there directly — removes the undeclared ddTraceMetricsOtelFlushInterval setAndTrack write - Update tests to use the env-var key names and remove the now-irrelevant config override test Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(lint): remove blank line before closing brace in config spec Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(config): generate GeneratedEnvVarConfig interface for env var types - Extract getBaseType helper from getTypeForEntry to share base type computation - Add getEnvVarType that only adds undefined when there is no registered default - Add generateEnvVarConfigTypes to map every env var name (canonical + aliases) to its resolved type - Append GeneratedEnvVarConfig interface to generated-config-types.d.ts Rationale: Callers of getValueFromEnvSources need per-env-var typed return values instead of the full config property union, enabling type-safe lookups by literal env var name. This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): address PR review comments - Gate OTLP-only SpanAggKey dimensions (origin, spanKind, rpcMethod, rpcStatusCode) on otlpTraceMetricsEnabled to avoid inflating legacy span stats aggregation key cardinality - Thread _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL through the typed config system (via setAndTrack/getValueFromEnvSources) instead of reading the raw env var directly in SpanStatsProcessor - Add config tests covering OTEL_TRACES_SPAN_METRICS_ENABLED auto-enable logic (both conditions, explicit override) Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(span-stats): pass otlpEnabled=true in transformer test bucket helper makeBucket is used exclusively by OtlpStatsTransformer tests, so spans must be keyed with otlpEnabled=true to populate the OTLP-gated fields (origin, spanKind, rpcMethod, rpcStatusCode). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * style(span-stats): minor style and readability cleanups - Extract flush interval to variable before setAndTrack call - Remove unnecessary quotes on property key - Tighten test description wording Rationale: Small consistency and readability improvements from PR review This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(span-stats): remove rpc.method from otlp span stats aggregation key - Drop grpc.method.name from SpanAggKey and OtlpStatsTransformer - rpc.method inflates aggregation key cardinality without sufficient benefit - Update all affected test assertions This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * refactor(span-stats): remove stale comments and clarify TODO - Remove redundant inline comments in otlp-span-stats transformer - Replace misleading comment about OTLP-only dimensions with a TODO noting origin and spanKind should eventually be included in legacy client stats aggregation This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * Apply suggestion from @mabdinur * refactor(span-stats): remove redundant inline comments - Drop comments that restate what the code already shows - Keep code self-documenting per project style guidelines This commit made by [/dd:git:commit:quick](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/quick.md) * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): fix max-len lint violation in span_processor.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: add otlp-span-stats exporter to CODEOWNERS Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): encapsulate OTLP span stats in opentelemetry/metrics Move OtlpStatsExporter and OtlpStatsTransformer into opentelemetry/metrics/ so the opentelemetry package is self-contained ahead of potential extraction into its own npm package. Key changes: - Move exporters/otlp-span-stats/{index,transformer}.js to opentelemetry/metrics/otlp_span_stats_{exporter,transformer}.js - Move buildResourceAttributes from span_stats.js to opentelemetry/metrics/index.js; add createOtlpSpanStatsExporter factory there - Wire OtlpStatsExporter via DI: opentracing/tracer.js creates it when OTEL_TRACES_SPAN_METRICS_ENABLED and passes it through SpanProcessor to SpanStatsProcessor — span_stats.js no longer imports from opentelemetry/ - config/index.js mirrors OTEL_TRACES_SPAN_METRICS_ENABLED into stats.DD_TRACE_STATS_COMPUTATION_ENABLED so downstream checks are unified - Remove otlpEnabled flag from SpanAggKey/SpanBuckets — origin, spanKind, rpcStatusCode are always populated - Remove OTEL-specific check from AgentExporter (relies on mirrored flag) - Remove CODEOWNERS entry for deleted exporters/otlp-span-stats/ path - Move tests to test/opentelemetry/metrics/ Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): align grpc stats with libdatadog and enforce mutual exclusion - Move GRPC_STATUS_CODE constant to ext/tags.js - Emit rpc.response.status_code as string (aligns with libdatadog kv_str) - Use else if in onInterval to make native and OTLP export mutually exclusive Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): trim JSDoc, privatize transformer, drop empty description - Make OtlpStatsExporter#transformer a private field (#transformer) - Remove empty description field from histogram metric - Trim redundant @param prose in exporter and transformer JSDoc - Use GRPC_STATUS_CODE import in transformer spec instead of string literal Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): suppress self-instrumentation spans from OTLP exporter requests Wrap sendPayload's HTTP request in legacyStorage.run({ noop: true }) so the tracer does not instrument its own outbound connections to the OTLP collector. Without this, tcp.connect client spans for /v1/metrics requests were fed into the traces.span.sdk.metrics.duration histogram, displacing real span data points and inflating counts in the system-tests parametric suite. Same pattern used by exporters/common/request.js and exporters/common/agents.js. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(opentelemetry): improve patch coverage for span stats OTLP export - Inline URL parsing into OtlpHttpExporterBase constructor; remove setUrl (the if(telemetryTags !== undefined) branch was dead — telemetryTags is always undefined when setUrl was called from the constructor, and no external caller ever invoked it post-construction) - Add tests for buildResourceAttributes (sdk identity, runtime-id, OTel- semantics mode) and createOtlpSpanStatsExporter - Add tests for HTTP error response and request error paths in sendPayload Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(opentelemetry): emit raw grpc.status.code name for rpc.response.status_code Prefer the meta status NAME string over the numeric metrics tag and emit it upper-cased to rpc.response.status_code, aligning with the OTel gRPC semantic conventions (canonical status name) without any code<->name mapping. * fix(opentelemetry): restore setUrl method removed by dead-code cleanup Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): trim redundant comments in grpc status mapping Co-authored-by: Cursor <[email protected]> * refactor(opentelemetry): read grpc.status.code from meta only Drop the numeric metrics fallback; the gRPC status code is the canonical status NAME and is read from span meta. Co-authored-by: Cursor <[email protected]> * fix(opentelemetry): translate numeric grpc.status.code from metrics to status name The dd gRPC plugin sets grpc.status.code as a numeric integer via span.setTag, which span_format.js routes into span.metrics rather than span.meta. SpanAggKey was reading meta only, so rpcStatusCode was always empty for real gRPC spans. Now falls back to span.metrics[GRPC_STATUS_CODE] and translates the integer to the canonical status name (OK, NOT_FOUND, etc.) using the gRPC status code table. Meta string takes priority when both are present. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): split agg key by top-level to fix mixed-bucket OTLP metrics When a bucket contained both top-level and measured non-top-level spans, the heuristic (topLevelHits === hits) always resolved to false, causing the OTLP histogram to be emitted as datadog.span.top_level=false and dropping top-level traffic from APM metrics. Adding topLevel as a dimension to SpanAggKey causes top-level and non-top-level spans to bucket separately. Each bucket is now always purely top-level or purely non-top-level, so the attribute is always accurate. The native stats path is unaffected because toJSON() omits topLevel; the Agent merges groups with identical key fields, preserving the same Hits/TopLevelHits totals. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): track per-top-level distributions to fix native stats regression The previous fix added topLevel to SpanAggKey to separate top-level and non-top-level spans into distinct buckets. This created a real regression in the native /v0.6/stats path: the Agent's mergeDuplicates() correctly sums Hits/Errors/Duration from duplicate rows but silently drops TopLevelHits from the merged-away entry. If the non-top-level row is processed first and becomes the canonical, TopLevelHits from the top-level row is lost. Fix: revert topLevel from SpanAggKey (no more duplicate rows). Instead, split SpanAggStats into four distributions (topLevelOk, topLevelError, nonTopLevelOk, nonTopLevelError). The native stats path merges them at export time so toJSON() produces the same combined OkSummary/ErrorSummary as before. The OTLP path emits separate data points per top-level status with the correct datadog.span.top_level attribute. OTel-semantics mode merges the distributions (no top-level attribute to distinguish them). Also adds SpanKind, Origin, and RpcStatusCode to the native stats toJSON() payload so the Agent receives these new dimensions. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(opentelemetry): split native stats rows by top-level status; use GRPCStatusCode key toJSON() now returns an array of up to 2 rows (top-level row first, non-top-level row second). #toLegacyPayload uses flatMap to flatten them. This eliminates the merge-time DDSketch allocation and ensures TopLevelHits is always non-zero on the top-level row, so the Agent's mergeDuplicates retains it as the canonical entry. Duration and Errors are derived from distribution .sum/.count, removing the redundant this.duration and this.errors accumulators. GRPCStatusCode matches the agent's msgpack decoder key (confirmed from pkg/proto/pbgo/trace/stats_gen.go). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(opentelemetry): minimize comments, move GRPC_STATUS_NAMES to constants - Remove descriptive/narrating comments throughout; keep only non-obvious constraints - Move GRPC_STATUS_NAMES from span_stats.js into constants.js - Rename #toLegacyPayload -> #v06Payload - Remove section comment from ext/tags.js Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(opentelemetry): restrict ORIGIN_KEY to synthetics boolean; remove origin from aggregation key ORIGIN_KEY now only populates SpanAggKey.synthetics. The origin string field is removed from SpanAggKey, toString(), and the v0.6 payload (Origin is not a field the agent decodes). In the OTLP path, datadog.origin='synthetics' is emitted when aggKey.synthetics is true. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore: remove pr_description.md --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Munir <[email protected]> Co-authored-by: Cursor <[email protected]>
What does this PR do?
Adds OTLP span metrics export and extends the existing trace stats aggregation key with new dimensions (
span.kind,rpc.response.status_code,origin) to bring dd-trace-js to parity with other Datadog SDKs.Motivation
When
OTEL_TRACES_EXPORTER=otlpis active, the Datadog Agent no longer receives/v0.6/stats— span metrics that APM depends on are lost. This PR adds a native JS implementation that aggregates span stats and emits them as atraces.span.sdk.metrics.durationdelta histogram to/v1/metrics. It is a bridge until the libdatadog-based concentrator lands in dd-trace-js; the aggregation key and wire format are kept in sync with libdatadog to make that a drop-in swap.Additional Notes
Activation:
OTEL_TRACES_SPAN_METRICS_ENABLED=true, or auto-enabled when bothOTEL_TRACES_EXPORTER=otlpandDD_METRICS_OTEL_ENABLED=trueare set.Mutual exclusion: the OTLP and native
/v0.6/statsexport paths are mutually exclusive. When the OTLP exporter is injected, the nativeSpanStatsExporteris never constructed andonIntervaluseselse ifto enforce this structurally.New aggregation dimensions (both modes):
span.kind,origin, andrpc.response.status_code(fromgrpc.status.code, emitted as string per libdatadog semantics) are now always included inSpanAggKey. The native/v0.6/statswire payload is unchanged; only the JS-side bucketing is more granular.Agent coordination:
_dd.stats_computed: "true"is added as a resource attribute on OTLP trace exports when active, preventing the Agent's OTLP receiver from double-counting.Datadog-Client-Computed-Stats: yesis still sent on the native path.Histogram:
count/sum/min/maxare DDSketch exact scalars. Bucket distribution is projected onto fixed explicit bounds matching libdatadog'sEXPLICIT_BOUNDS_SECONDS.Code structure: all OTLP-specific code lives under
packages/dd-trace/src/opentelemetry/metrics/.SpanStatsProcessorreceives the exporter via DI fromopentracing/tracer.js;span_stats.jshas no imports fromopentelemetry/.OTel semantics mode: when
DD_TRACE_OTEL_SEMANTICS_ENABLED=true,dd.*attributes are omitted from trace metrics data points.Known gaps (tracked separately): cross-service entry spans not tagged top-level (FR06.3); p0 traces not dropped when client stats are active (FR09.1).
Tests:
test/span_stats.spec.js,test/opentelemetry/metrics/otlp_span_stats_exporter.spec.js,test/opentelemetry/metrics/otlp_span_stats_transformer.spec.js. Cross-tracer coverage via the system-tests parametric suite.