Skip to content

Commit f408684

Browse files
authored
fix(otel): correct sampling priority and origin propagation from trac… (#8031)
Three related bugs in the OTel-to-Datadog context bridge are fixed: 1. `SpanContext` is a composition wrapper that exposes `traceFlags` via `this._ddContext._sampling.priority`. The extractor was writing `_sampling` and `_trace` directly on the wrapper, so the values never reached `_ddContext` and `traceFlags` always returned 0 regardless of the `s:` field in the incoming tracestate. 2. `Number.parseInt(samplingPriorityTs, 10)` produced `NaN` when the `dd` tracestate member had no `s:` field. The `NaN` was then passed to `_getSamplingPriority`, where it slipped through both conditional branches and was returned as-is. `_getSamplingPriority` now uses the existing `AUTO_REJECT`/`AUTO_KEEP` constants and defaults to `AUTO_KEEP` when the tracestate priority is missing or not a number. The caller in `_convertOtelContextToDatadog` guards `parseInt` to avoid producing `NaN` in the first place. 3. The `origin` pulled from the `dd` tracestate (`o:` field) was being assigned to an inner block-scoped `const origin`, which shadowed the outer `origin` variable used when writing `_trace`. As a result the origin was silently dropped from every extracted OTel context.
1 parent 9f10b0e commit f408684

3 files changed

Lines changed: 75 additions & 21 deletions

File tree

packages/dd-trace/src/opentelemetry/tracer.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ class Tracer {
8181
}
8282

8383
_convertOtelContextToDatadog (traceId, spanId, traceFlag, ts, meta = {}) {
84-
const origin = null
84+
let origin = null
8585
let samplingPriority = traceFlag
8686

87-
ts = ts?.traceparent || null
87+
ts = ts?.traceparent
8888

8989
if (ts) {
9090
// Use TraceState.fromString to parse the tracestate header
@@ -101,28 +101,26 @@ class Tracer {
101101
// Assuming ddTraceStateData is now a Map or similar structure containing Datadog trace state data
102102
// Extract values as needed, similar to the original logic
103103
const samplingPriorityTs = ddTraceStateData.get('s')
104-
const origin = ddTraceStateData.get('o')
104+
origin = ddTraceStateData.get('o') ?? null
105105
// Convert Map to object for meta
106106
const otherPropagatedTags = Object.fromEntries(ddTraceStateData.entries())
107107

108108
// Update meta and samplingPriority based on extracted values
109109
Object.assign(meta, otherPropagatedTags)
110-
samplingPriority = TextMapPropagator._getSamplingPriority(
111-
traceFlag,
112-
Number.parseInt(samplingPriorityTs, 10),
113-
origin
114-
)
110+
// Guard against an undefined/empty `s:` field that would result in NaN.
111+
const tracestateSamplingPriority = samplingPriorityTs ? Math.trunc(samplingPriorityTs) : undefined
112+
samplingPriority = TextMapPropagator._getSamplingPriority(traceFlag, tracestateSamplingPriority, origin)
115113
} else {
116-
log.debug('no dd list member in tracestate from incoming request:', ts)
114+
log.debug('No dd list member in tracestate from incoming request:', ts)
117115
}
118116
}
119117

120118
const spanContext = new SpanContext({
121119
traceId: id(traceId, 16), spanId: id(), tags: meta, parentId: id(spanId, 16),
122120
})
123121

124-
spanContext._sampling = { priority: samplingPriority }
125-
spanContext._trace = { origin }
122+
spanContext._ddContext._sampling = { priority: samplingPriority }
123+
spanContext._ddContext._trace = { ...spanContext._ddContext._trace, origin }
126124
return spanContext
127125
}
128126

packages/dd-trace/src/opentracing/propagation/text_map.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -802,18 +802,25 @@ class TextMapPropagator {
802802
return spanContext._traceId.toString(16)
803803
}
804804

805-
static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin = null) {
805+
/**
806+
* @param {number} traceparentSampled
807+
* @param {number|undefined} tracestateSamplingPriority
808+
* @param {string|null} origin
809+
* @returns {import('../../priority_sampler').SamplingPriority}
810+
*/
811+
static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin) {
806812
const fromRumWithoutPriority = !tracestateSamplingPriority && origin === 'rum'
807813

808-
let samplingPriority
809-
if (!fromRumWithoutPriority && traceparentSampled === 0 &&
810-
(!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
811-
samplingPriority = 0
812-
} else if (!fromRumWithoutPriority && traceparentSampled === 1 &&
813-
(!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
814-
samplingPriority = 1
815-
} else {
816-
samplingPriority = tracestateSamplingPriority
814+
let samplingPriority =
815+
/** @type {import('../../priority_sampler').SamplingPriority} */ (tracestateSamplingPriority ?? AUTO_KEEP)
816+
if (!fromRumWithoutPriority) {
817+
if (traceparentSampled === 0 &&
818+
(!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
819+
samplingPriority = AUTO_REJECT
820+
} else if (traceparentSampled === 1 &&
821+
(!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
822+
samplingPriority = AUTO_KEEP
823+
}
817824
}
818825

819826
return samplingPriority

packages/dd-trace/test/opentelemetry/tracer.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const sinon = require('sinon')
88
const api = require('@opentelemetry/api')
99

1010
const { hrTime, timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/core')
11+
const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../ext/priority')
1112
const { storage } = require('../../../datadog-core')
1213
require('../setup/core')
1314
require('../../').init()
@@ -214,6 +215,54 @@ describe('OTel Tracer', () => {
214215
})
215216
})
216217

218+
describe('_convertOtelContextToDatadog (traceparent/tracestate extraction)', () => {
219+
const TRACE_ID = '0123456789abcdef0123456789abcdef'
220+
const SPAN_ID = '0123456789abcdef'
221+
222+
/**
223+
* @param {number} traceFlag
224+
* @param {string|null} tracestate
225+
*/
226+
function convert (traceFlag, tracestate) {
227+
const otelTracer = new Tracer({}, {}, new TracerProvider())
228+
return otelTracer._convertOtelContextToDatadog(
229+
TRACE_ID,
230+
SPAN_ID,
231+
traceFlag,
232+
tracestate ? { traceparent: tracestate } : null
233+
)
234+
}
235+
236+
it('writes sampling priority onto the wrapped Datadog context', () => {
237+
const spanContext = convert(1, 'other=bleh,dd=s:2;o:synthetics;t.dm:-4')
238+
assert.strictEqual(spanContext._ddContext._sampling.priority, USER_KEEP)
239+
assert.strictEqual(spanContext._ddContext._trace.origin, 'synthetics')
240+
assert.strictEqual(spanContext.traceFlags, 1)
241+
})
242+
243+
it('preserves the existing _trace.started/finished/tags when writing origin', () => {
244+
const spanContext = convert(1, 'other=bleh,dd=s:1;o:foo')
245+
assert.deepStrictEqual(spanContext._ddContext._trace.started, [])
246+
assert.deepStrictEqual(spanContext._ddContext._trace.finished, [])
247+
assert.deepStrictEqual(spanContext._ddContext._trace.tags, {})
248+
assert.strictEqual(spanContext._ddContext._trace.origin, 'foo')
249+
})
250+
251+
it('falls back to AUTO_REJECT/AUTO_KEEP when tracestate has no s: field', () => {
252+
const rejected = convert(0, 'other=bleh,dd=o:foo;t.dm:-4')
253+
assert.strictEqual(rejected._ddContext._sampling.priority, AUTO_REJECT)
254+
255+
const kept = convert(1, 'other=bleh,dd=o:foo;t.dm:-4')
256+
assert.strictEqual(kept._ddContext._sampling.priority, AUTO_KEEP)
257+
})
258+
259+
it('falls back to AUTO_KEEP for RUM traces without a priority', () => {
260+
const spanContext = convert(1, 'other=bleh,dd=o:rum')
261+
assert.strictEqual(spanContext._ddContext._sampling.priority, AUTO_KEEP)
262+
assert.strictEqual(spanContext._ddContext._trace.origin, 'rum')
263+
})
264+
})
265+
217266
it('test otel context mixed span parenting', () => {
218267
const tracerProvider = new TracerProvider()
219268
tracerProvider.register()

0 commit comments

Comments
 (0)