Skip to content

Commit 658450e

Browse files
authored
fix(otel): return a non-recording span when the inner tracer is the noop (#8218)
When `tracer.init()` is never called or runs with `DD_TRACE_ENABLED=false`, the proxy's inner tracer stays a `NoopTracer` (no `_processor`, no `_prioritySampler`). The OTel bridge still constructs a `DatadogSpan` against it; the bridge `Span` constructor's first `setAttributes` call crashes inside `_addTags` with `Cannot read properties of undefined (reading 'sample')`. The bridge now returns `api.trace.wrapSpanContext(spanContext)` instead -- the shape OTel uses for a `NOT_RECORD` sampling decision. Propagation keeps working, mutation methods become no-ops, and the disabled path never allocates a `DatadogSpan`. Fixes: #3854
1 parent fe48fc0 commit 658450e

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const api = require('@opentelemetry/api')
44
const { sanitizeAttributes } = require('../../../../vendor/dist/@opentelemetry/core')
55

6+
const tracer = require('../../')
7+
68
const id = require('../id')
79
const log = require('../log')
810
const DatadogSpanContext = require('../opentracing/span_context')
@@ -140,6 +142,14 @@ class Tracer {
140142
spanContext = new SpanContext()
141143
}
142144

145+
// init() didn't finish setting up real tracing (e.g. DD_TRACE_ENABLED=false,
146+
// or init() was never called), so the inner tracer is still the noop.
147+
// DatadogSpan can't construct without a processor + prioritySampler, so fall
148+
// through to a non-recording span; the SpanContext still propagates.
149+
if (!tracer._tracingInitialized) {
150+
return api.trace.wrapSpanContext(spanContext)
151+
}
152+
143153
const spanKind = options.kind || api.SpanKind.INTERNAL
144154
const links = []
145155
if (options.links?.length) {

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require('../../').init()
1515
const TracerProvider = require('../../src/opentelemetry/tracer_provider')
1616
const Tracer = require('../../src/opentelemetry/tracer')
1717
const Span = require('../../src/opentelemetry/span')
18+
const NoopTracer = require('../../src/noop/tracer')
1819
const DatadogSpan = require('../../src/opentracing/span')
1920
const tracer = require('../../')
2021

@@ -73,6 +74,30 @@ describe('OTel Tracer', () => {
7374
assert.strictEqual(ddSpanContext._tags.foo, 'bar')
7475
})
7576

77+
it('returns a non-recording span when the inner tracer is the noop', () => {
78+
const originalTracer = tracer._tracer
79+
const originalInitialized = tracer._tracingInitialized
80+
tracer._tracer = new NoopTracer()
81+
tracer._tracingInitialized = false
82+
try {
83+
const otelTracer = new Tracer({}, {}, new TracerProvider())
84+
const span = otelTracer.startSpan('name', { attributes: { foo: 'bar' } })
85+
86+
assert.strictEqual(span instanceof Span, false)
87+
assert.strictEqual(span.isRecording(), false)
88+
assert.ok(api.trace.isSpanContextValid(span.spanContext()))
89+
90+
span.setAttribute('after', 'create')
91+
span.setAttributes({ baz: 'qux' })
92+
span.addEvent('event')
93+
span.recordException(new Error('oops'))
94+
span.end()
95+
} finally {
96+
tracer._tracer = originalTracer
97+
tracer._tracingInitialized = originalInitialized
98+
}
99+
})
100+
76101
it('should pass through span kind', () => {
77102
const tracerProvider = new TracerProvider()
78103
const otelTracer = new Tracer({}, {}, tracerProvider)

0 commit comments

Comments
 (0)