From 753e471d5eb9868929db950c17e3ea45472f2c30 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sat, 28 Mar 2026 18:58:12 -0400 Subject: [PATCH 01/48] internal API split for Span and Scope --- packages/dd-trace/src/constants.js | 1 + packages/dd-trace/src/noop/proxy.js | 37 +- .../dd-trace/src/opentracing/public/scope.js | 30 ++ .../dd-trace/src/opentracing/public/span.js | 61 +++ packages/dd-trace/test/proxy.spec.js | 353 +++++++++++++++++- 5 files changed, 464 insertions(+), 18 deletions(-) create mode 100644 packages/dd-trace/src/opentracing/public/scope.js create mode 100644 packages/dd-trace/src/opentracing/public/span.js diff --git a/packages/dd-trace/src/constants.js b/packages/dd-trace/src/constants.js index a4fe59187c7..2d1237f580f 100644 --- a/packages/dd-trace/src/constants.js +++ b/packages/dd-trace/src/constants.js @@ -30,6 +30,7 @@ module.exports = { ERROR_STACK: 'error.stack', IGNORE_OTEL_ERROR: Symbol('ignore.otel.error'), COMPONENT: 'component', + SVC_SRC_KEY: '_dd.svc_src', CLIENT_PORT_KEY: 'network.destination.port', PEER_SERVICE_KEY: 'peer.service', PEER_SERVICE_SOURCE_KEY: '_dd.peer.service.source', diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index b223aab3a65..ee423984986 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -4,7 +4,10 @@ const NoopAppsecSdk = require('../appsec/sdk/noop') const NoopLLMObsSDK = require('../llmobs/noop') const NoopFlaggingProvider = require('../openfeature/noop') const NoopAIGuardSDK = require('../aiguard/noop') +const PublicSpan = require('../opentracing/public/span') +const PublicScope = require('../opentracing/public/scope') const NoopDogStatsDClient = require('./dogstatsd') +const { SVC_SRC_KEY } = require('../../src/constants') const NoopTracer = require('./tracer') const noop = new NoopTracer() @@ -51,8 +54,17 @@ class NoopProxy { if (typeof fn !== 'function') return options = options || {} + if (options.service || options?.tags?.service || options?.tags?.['service.name']) { + options.tags = { + ...options.tags, + [SVC_SRC_KEY]: 'm', + } + } - return this._tracer.trace(name, options, fn) + const callback = (span, done) => { + return fn(new PublicSpan(span), done) + } + return this._tracer.trace(name, options, callback) } wrap (name, options, fn) { @@ -64,8 +76,17 @@ class NoopProxy { if (typeof fn !== 'function') return fn options = options || {} + if (options.service || options?.tags?.service || options?.tags?.['service.name']) { + options.tags = { + ...options.tags, + [SVC_SRC_KEY]: 'm', + } + } + const callback = (span, done) => { + return fn(new PublicSpan(span), done) + } - return this._tracer.wrap(name, options, fn) + return this._tracer.wrap(name, options, callback) } setUrl () { @@ -73,8 +94,14 @@ class NoopProxy { return this } - startSpan () { - return this._tracer.startSpan.apply(this._tracer, arguments) + startSpan (name, options) { + if (options?.tags?.service || options?.tags?.['service.name']) { + options.tags = { + ...options.tags, + [SVC_SRC_KEY]: 'm', + } + } + return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) } inject () { @@ -86,7 +113,7 @@ class NoopProxy { } scope () { - return this._tracer.scope.apply(this._tracer, arguments) + return new PublicScope(this._tracer.scope.apply(this._tracer, arguments)) } getRumData () { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js new file mode 100644 index 00000000000..b9d49d09a17 --- /dev/null +++ b/packages/dd-trace/src/opentracing/public/scope.js @@ -0,0 +1,30 @@ +'use strict' + +const PublicSpan = require('./span') + +class Scope { + constructor (scope) { + this._scope = scope + } + + active () { + const span = this._scope.active() + return span ? new PublicSpan(span) : null + } + + activate (span, fn) { + if (span instanceof PublicSpan) { + span = span._span + } + return this._scope.activate(span, fn) + } + + bind (fn, span) { + if (span instanceof PublicSpan) { + span = span._span + } + return this._scope.bind(fn, span) + } +} + +module.exports = Scope diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js new file mode 100644 index 00000000000..77856669f73 --- /dev/null +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -0,0 +1,61 @@ +'use strict' + +const { SVC_SRC_KEY } = require('../../constants') + +const SERVICE_KEY = 'service' +const SERVICE_NAME_KEY = 'service.name' + +/** + * This is a public wrapper of Span, this allows distinguishing internal usage from + * external usage and acting accordingly. + */ +class PublicSpan { + constructor (span) { + this._span = span + } + + setTag (key, value) { + if (key === SERVICE_KEY || key === SERVICE_NAME_KEY) { + this._span.setTag(SVC_SRC_KEY, 'm') + } + this._span.setTag(key, value) + return this + } + + addTags (tags) { + if (tags[SERVICE_KEY] || tags[SERVICE_NAME_KEY]) { + this._span.setTag(SVC_SRC_KEY, 'm') + } + this._span.addTags(tags) + return this + } +} + +// Whenever a method needs to be modified to have a unique public behavior, it +// should be removed from this list. +for (const method of [ + 'context', + 'tracer', + 'setOperationName', + 'setBaggageItem', + 'getBaggageItem', + 'getAllBaggageItems', + 'removeBaggageItem', + 'removeAllBaggageItems', + 'log', + 'logEvent', + 'addLink', + 'addLinks', + 'addSpanPointer', + 'addEvent', + 'finish', + 'toString' +]) { + PublicSpan.prototype[method] = function (...args) { + const result = this._span[method](...args) + // always return wrapper span when the result is the span itself + return result === this._span ? this : result + } +} + +module.exports = PublicSpan diff --git a/packages/dd-trace/test/proxy.spec.js b/packages/dd-trace/test/proxy.spec.js index fe80590fd37..1ecaa9d809f 100644 --- a/packages/dd-trace/test/proxy.spec.js +++ b/packages/dd-trace/test/proxy.spec.js @@ -103,6 +103,9 @@ describe('TracerProxy', () => { flush: sinon.spy(), } + tracer.scope = sinon.stub() + noop.scope = sinon.stub() + { const dogstatsdIncrements = [] let dogstatsdConfig @@ -620,18 +623,41 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.trace('a', 'b', callback) - sinon.assert.calledWith(noop.trace, 'a', 'b', callback) + sinon.assert.calledWith(noop.trace, 'a', 'b', sinon.match.func) assert.strictEqual(returnValue, 'test') }) it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.trace('a', callback) - - sinon.assert.calledWith(noop.trace, 'a', {}, callback) + sinon.assert.calledWith(noop.trace, 'a', {}, sinon.match.func) assert.strictEqual(returnValue, 'test') }) + it('should set service source override tag when service option is provided', () => { + const callback = () => 'test' + proxy.trace('a', { service: 'b' }, callback) + + sinon.assert.calledWith(noop.trace, 'a', { + service: 'b', + tags: { + '_dd.svc_src': 'm', + }, + }, sinon.match.func) + assert.notStrictEqual(noop.trace.firstCall.args[2], callback) + }) + + it('should set service source tag when the span inside callback does a service override', () => { + const spanStub = { setTag: sinon.stub() } + const setTagSpy = spanStub.setTag + + noop.trace.callsFake((name, options, callback) => callback(spanStub)) + proxy.trace('a', span => span.setTag('service', 'b')) + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + it('should ignore calls without an invalid callback', () => { proxy.wrap('a', 'b') @@ -644,7 +670,7 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', 'b', callback) - sinon.assert.calledWith(noop.wrap, 'a', 'b', callback) + sinon.assert.calledWith(noop.wrap, 'a', 'b', sinon.match.func) assert.strictEqual(returnValue, 'fn') }) @@ -652,10 +678,37 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', callback) - sinon.assert.calledWith(noop.wrap, 'a', {}, callback) + sinon.assert.calledWith(noop.wrap, 'a', {}, sinon.match.func) + assert.strictEqual(returnValue, 'fn') + }) + + it('should add _dd.svc_src to tags when a service override is provided through options', () => { + const callback = () => 'test' + const returnValue = proxy.wrap('a', { + service: 'custom-service', + }, callback) + + sinon.assert.calledWith(noop.wrap, 'a', { + service: 'custom-service', + tags: { + '_dd.svc_src': 'm', + }, + }, sinon.match.func) + assert.notStrictEqual(noop.wrap.firstCall.args[2], callback) assert.strictEqual(returnValue, 'fn') }) + it('should patch the wrap callback span setTag when a service override is provided', () => { + const spanStub = { setTag: sinon.spy() } + const setTagSpy = spanStub.setTag + noop.wrap.callsFake((name, options, callback) => callback(spanStub)) + + proxy.wrap('a', (span, done) => span.setTag('service', 'b')) + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + it('should ignore calls without an invalid callback', () => { const returnValue = proxy.wrap('a', 'b') @@ -669,7 +722,30 @@ describe('TracerProxy', () => { const returnValue = proxy.startSpan('a', 'b', 'c') sinon.assert.calledWith(noop.startSpan, 'a', 'b', 'c') - assert.strictEqual(returnValue, 'span') + assert.strictEqual(returnValue._span, 'span') + }) + + it('should set service source override tag when returned span does a setTag', () => { + const spanStub = { setTag: sinon.spy() } + noop.startSpan.returns(spanStub) + const setTagSpy = spanStub.setTag + + const returnValue = proxy.startSpan('a', 'b', 'c') + returnValue.setTag('service', 'b') + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + + it('should not set service source override tag when returned span does not setTag', () => { + const spanStub = { setTag: sinon.spy() } + noop.startSpan.returns(spanStub) + const setTagSpy = spanStub.setTag + + const returnValue = proxy.startSpan('a', 'b', 'c') + returnValue.setTag('randomTag', 'b') + assert.strictEqual(setTagSpy.callCount, 1) + sinon.assert.calledWith(setTagSpy.firstCall, 'randomTag', 'b') }) }) @@ -692,11 +768,100 @@ describe('TracerProxy', () => { }) describe('setUrl', () => { - it('should call the underlying DatadogTracer', () => { + it('should call the underlying NoopTracer', () => { const returnValue = proxy.setUrl('http://example.com') - sinon.assert.calledWith(noop.setUrl, 'http://example.com') assert.strictEqual(returnValue, proxy) + sinon.assert.calledWith(noop.setUrl, 'http://example.com') + }) + }) + + describe('scope', () => { + it('should wrap the underlying noop scope', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub(), + bind: sinon.stub(), + } + + noop.scope.returns(scope) + + const returnValue = proxy.scope() + + assert.notStrictEqual(returnValue, scope) + sinon.assert.calledOnce(noop.scope) + assert.strictEqual(returnValue._scope, scope) + }) + + it('should expose wrapped active spans and apply public span behavior', () => { + const span = { + context: sinon.stub().returns('context'), + setTag: sinon.spy(), + } + const scope = { + active: sinon.stub().returns(span), + activate: sinon.stub(), + bind: sinon.stub(), + } + + noop.scope.returns(scope) + + const activeSpan = proxy.scope().active() + + assert.notStrictEqual(activeSpan, span) + assert.strictEqual(activeSpan._span, span) + assert.strictEqual(activeSpan.context(), 'context') + + activeSpan.setTag('service', 'test-service') + + assert.strictEqual(span.setTag.callCount, 2) + sinon.assert.calledWith(span.setTag.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(span.setTag.secondCall, 'service', 'test-service') + sinon.assert.calledOnce(scope.active) + }) + + it('should unwrap public spans when activating a scope', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub().callsFake((span, fn) => fn()), + bind: sinon.stub(), + } + const internalSpan = { + context: sinon.stub(), + } + + noop.scope.returns(scope) + noop.startSpan.returns(internalSpan) + + const span = proxy.startSpan('test') + + const returnValue = proxy.scope().activate(span, () => 'result') + + assert.strictEqual(returnValue, 'result') + sinon.assert.calledOnce(scope.activate) + sinon.assert.calledWith(scope.activate, internalSpan, sinon.match.func) + }) + + it('should unwrap public spans when binding functions', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub(), + bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), + } + const internalSpan = { + context: sinon.stub(), + } + const fn = sinon.stub() + + noop.scope.returns(scope) + noop.startSpan.returns(internalSpan) + + const span = proxy.startSpan('test') + const returnValue = proxy.scope().bind(fn, span) + + assert.deepStrictEqual(returnValue, { fn, span: internalSpan }) + sinon.assert.calledOnce(scope.bind) + sinon.assert.calledWith(scope.bind, fn, internalSpan) }) }) @@ -845,15 +1010,39 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.trace('a', 'b', callback) - sinon.assert.calledWith(tracer.trace, 'a', 'b', callback) + sinon.assert.calledWith(tracer.trace, 'a', 'b', sinon.match.func) assert.strictEqual(returnValue, 'test') }) + it('should set service source override tag when service option is provided', () => { + const callback = () => 'test' + proxy.trace('a', { service: 'b' }, callback) + + sinon.assert.calledWith(tracer.trace, 'a', { + service: 'b', + tags: { + '_dd.svc_src': 'm', + }, + }, sinon.match.func) + assert.notStrictEqual(tracer.trace.firstCall.args[2], callback) + }) + + it('should set service source tag when the span inside callback does a service override', () => { + const spanStub = { setTag: sinon.stub() } + const setTagSpy = spanStub.setTag + + tracer.trace.callsFake((name, options, callback) => callback(spanStub)) + proxy.trace('a', span => span.setTag('service', 'b')) + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.trace('a', callback) - sinon.assert.calledWith(tracer.trace, 'a', {}, callback) + sinon.assert.calledWith(tracer.trace, 'a', {}, sinon.match.func) assert.strictEqual(returnValue, 'test') }) }) @@ -863,15 +1052,42 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', 'b', callback) - sinon.assert.calledWith(tracer.wrap, 'a', 'b', callback) + sinon.assert.calledWith(tracer.wrap, 'a', 'b', sinon.match.func) assert.strictEqual(returnValue, 'fn') }) + it('should add _dd.svc_src to tags when a service override is provided through options', () => { + const callback = () => 'test' + const returnValue = proxy.wrap('a', { + service: 'custom-service', + }, callback) + + sinon.assert.calledWith(tracer.wrap, 'a', { + service: 'custom-service', + tags: { + '_dd.svc_src': 'm', + }, + }, sinon.match.func) + assert.notStrictEqual(tracer.wrap.firstCall.args[2], callback) + assert.strictEqual(returnValue, 'fn') + }) + + it('should patch the wrap callback span setTag when a service override is provided', () => { + const spanStub = { setTag: sinon.spy() } + const setTagSpy = spanStub.setTag + tracer.wrap.callsFake((name, options, callback) => callback(spanStub)) + + proxy.wrap('a', (span, done) => span.setTag('service', 'b')) + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', callback) - sinon.assert.calledWith(tracer.wrap, 'a', {}, callback) + sinon.assert.calledWith(tracer.wrap, 'a', {}, sinon.match.func) assert.strictEqual(returnValue, 'fn') }) }) @@ -881,7 +1097,30 @@ describe('TracerProxy', () => { const returnValue = proxy.startSpan('a', 'b', 'c') sinon.assert.calledWith(tracer.startSpan, 'a', 'b', 'c') - assert.strictEqual(returnValue, 'span') + assert.strictEqual(returnValue._span, 'span') + }) + + it('should set service source override tag when returned span does a setTag', () => { + const spanStub = { setTag: sinon.spy() } + tracer.startSpan.returns(spanStub) + const setTagSpy = spanStub.setTag + + const returnValue = proxy.startSpan('a', 'b', 'c') + returnValue.setTag('service', 'b') + assert.strictEqual(setTagSpy.callCount, 2) + sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }) + + it('should not set service source override tag when returned span does not setTag', () => { + const spanStub = { setTag: sinon.spy() } + tracer.startSpan.returns(spanStub) + const setTagSpy = spanStub.setTag + + const returnValue = proxy.startSpan('a', 'b', 'c') + returnValue.setTag('randomTag', 'b') + assert.strictEqual(setTagSpy.callCount, 1) + sinon.assert.calledWith(setTagSpy.firstCall, 'randomTag', 'b') }) }) @@ -912,6 +1151,94 @@ describe('TracerProxy', () => { }) }) + describe('scope', () => { + it('should wrap the underlying tracer scope', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub(), + bind: sinon.stub(), + } + + tracer.scope.returns(scope) + + const returnValue = proxy.scope() + + assert.notStrictEqual(returnValue, scope) + sinon.assert.calledOnce(tracer.scope) + assert.strictEqual(returnValue._scope, scope) + }) + + it('should return wrapped active spans and apply public span behavior', () => { + const span = { + context: sinon.stub().returns('context'), + setTag: sinon.spy(), + } + const scope = { + active: sinon.stub().returns(span), + activate: sinon.stub(), + bind: sinon.stub(), + } + + tracer.scope.returns(scope) + + const activeSpan = proxy.scope().active() + + assert.notStrictEqual(activeSpan, span) + assert.strictEqual(activeSpan._span, span) + assert.strictEqual(activeSpan.context(), 'context') + + activeSpan.setTag('service', 'test-service') + + assert.strictEqual(span.setTag.callCount, 2) + sinon.assert.calledWith(span.setTag.firstCall, '_dd.svc_src', 'm') + sinon.assert.calledWith(span.setTag.secondCall, 'service', 'test-service') + sinon.assert.calledOnce(scope.active) + }) + + it('should unwrap public spans for activate', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub().callsFake((span, fn) => fn()), + bind: sinon.stub(), + } + const internalSpan = { + context: sinon.stub(), + } + + tracer.scope.returns(scope) + tracer.startSpan.returns(internalSpan) + + const span = proxy.startSpan('test') + const returnValue = proxy.scope().activate(span, () => 'result') + + assert.strictEqual(returnValue, 'result') + sinon.assert.calledOnce(scope.activate) + sinon.assert.calledWith(scope.activate, internalSpan, sinon.match.func) + }) + + it('should unwrap public spans for bind', () => { + const scope = { + active: sinon.stub().returns(null), + activate: sinon.stub(), + bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), + } + const internalSpan = { + context: sinon.stub(), + } + const fn = sinon.stub() + + tracer.scope.returns(scope) + tracer.startSpan.returns(internalSpan) + + const span = proxy.startSpan('test') + const returnValue = proxy.scope().bind(fn, span) + + assert.deepStrictEqual(returnValue, { fn, span: internalSpan }) + sinon.assert.calledOnce(scope.bind) + sinon.assert.calledWith(scope.bind, fn, internalSpan) + }) + }) + describe('appsec', () => { describe('trackUserLoginSuccessEvent', () => { it('should call the underlying AppsecSdk method', () => { From 29c0c6ba543bc7f86bef44243e4b6ddebb9cb2fe Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 16:22:25 -0400 Subject: [PATCH 02/48] removed the need of unwrapping span --- packages/dd-trace/src/noop/proxy.js | 6 +++--- packages/dd-trace/src/opentracing/public/scope.js | 8 +------- packages/dd-trace/src/opentracing/public/span.js | 12 ++++++++++++ packages/dd-trace/src/opentracing/tracer.js | 3 ++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index ee423984986..72b907ab6af 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -62,7 +62,7 @@ class NoopProxy { } const callback = (span, done) => { - return fn(new PublicSpan(span), done) + return fn(PublicSpan.wrap(span), done) } return this._tracer.trace(name, options, callback) } @@ -83,7 +83,7 @@ class NoopProxy { } } const callback = (span, done) => { - return fn(new PublicSpan(span), done) + return fn(PublicSpan.wrap(span), done) } return this._tracer.wrap(name, options, callback) @@ -101,7 +101,7 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) + return PublicSpan.wrap(this._tracer.startSpan.apply(this._tracer, arguments)) } inject () { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js index b9d49d09a17..7404f2078b4 100644 --- a/packages/dd-trace/src/opentracing/public/scope.js +++ b/packages/dd-trace/src/opentracing/public/scope.js @@ -9,20 +9,14 @@ class Scope { active () { const span = this._scope.active() - return span ? new PublicSpan(span) : null + return span ? PublicSpan.wrap(span) : null } activate (span, fn) { - if (span instanceof PublicSpan) { - span = span._span - } return this._scope.activate(span, fn) } bind (fn, span) { - if (span instanceof PublicSpan) { - span = span._span - } return this._scope.bind(fn, span) } } diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 77856669f73..4017a796a2b 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -14,6 +14,18 @@ class PublicSpan { this._span = span } + // This is needed for activate() + get _store () { return this._span._store } + + // This safely wraps a span, this is needed in cases i which active returns the same span resulting + // double wrapping + static wrap (span) { + if (span instanceof PublicSpan) { + return span + } + return new PublicSpan(span) + } + setTag (key, value) { if (key === SERVICE_KEY || key === SERVICE_NAME_KEY) { this._span.setTag(SVC_SRC_KEY, 'm') diff --git a/packages/dd-trace/src/opentracing/tracer.js b/packages/dd-trace/src/opentracing/tracer.js index d26f720ddba..3c4d5aa1183 100644 --- a/packages/dd-trace/src/opentracing/tracer.js +++ b/packages/dd-trace/src/opentracing/tracer.js @@ -8,6 +8,7 @@ const log = require('../log') const runtimeMetrics = require('../runtime_metrics') const getExporter = require('../exporter') const Span = require('./span') +const PublicSpan = require('./public/span') const TextMapPropagator = require('./propagation/text_map') const DSMTextMapPropagator = require('./propagation/text_map_dsm') const HttpPropagator = require('./propagation/http') @@ -114,7 +115,7 @@ class DatadogTracer { * @returns {SpanContext} */ function getContext (spanContext) { - if (spanContext instanceof Span) { + if (spanContext instanceof Span || spanContext instanceof PublicSpan) { spanContext = spanContext.context() } From e608c733e599a3fd7384a422159b864f3c722e28 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 16:37:06 -0400 Subject: [PATCH 03/48] fixed proxy tests --- packages/dd-trace/test/proxy.spec.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/dd-trace/test/proxy.spec.js b/packages/dd-trace/test/proxy.spec.js index 1ecaa9d809f..3d9650aaa5d 100644 --- a/packages/dd-trace/test/proxy.spec.js +++ b/packages/dd-trace/test/proxy.spec.js @@ -5,7 +5,7 @@ const assert = require('node:assert/strict') const { describe, it, beforeEach, afterEach } = require('mocha') const sinon = require('sinon') const proxyquire = require('proxyquire') - +const PublicSpan = require('../src/opentracing/public/span') require('./setup/core') describe('TracerProxy', () => { @@ -826,9 +826,9 @@ describe('TracerProxy', () => { activate: sinon.stub().callsFake((span, fn) => fn()), bind: sinon.stub(), } - const internalSpan = { + const internalSpan = new PublicSpan({ context: sinon.stub(), - } + }) noop.scope.returns(scope) noop.startSpan.returns(internalSpan) @@ -848,9 +848,9 @@ describe('TracerProxy', () => { activate: sinon.stub(), bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), } - const internalSpan = { + const internalSpan = new PublicSpan({ context: sinon.stub(), - } + }) const fn = sinon.stub() noop.scope.returns(scope) @@ -1201,9 +1201,9 @@ describe('TracerProxy', () => { activate: sinon.stub().callsFake((span, fn) => fn()), bind: sinon.stub(), } - const internalSpan = { + const internalSpan = new PublicSpan({ context: sinon.stub(), - } + }) tracer.scope.returns(scope) tracer.startSpan.returns(internalSpan) @@ -1222,9 +1222,9 @@ describe('TracerProxy', () => { activate: sinon.stub(), bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), } - const internalSpan = { + const internalSpan = new PublicSpan({ context: sinon.stub(), - } + }) const fn = sinon.stub() tracer.scope.returns(scope) From e486dfe2dd63ae5cc42e3eebd1025044834d6545 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 16:44:29 -0400 Subject: [PATCH 04/48] make tests comply with public api --- packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js | 2 +- packages/datadog-plugin-fs/test/index.spec.js | 4 ++-- packages/datadog-plugin-grpc/test/client.spec.js | 4 ++-- packages/datadog-plugin-http2/test/client.spec.js | 2 +- packages/datadog-plugin-ioredis/test/index.spec.js | 2 +- packages/datadog-plugin-iovalkey/test/index.spec.js | 2 +- packages/datadog-plugin-mongoose/test/index.spec.js | 12 ++++++------ packages/datadog-plugin-oracledb/test/index.spec.js | 4 ++-- packages/datadog-plugin-pg/test/index.spec.js | 2 +- packages/datadog-plugin-redis/test/client.spec.js | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js index a4ae8ac40d2..3c8c152b85f 100644 --- a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js @@ -206,7 +206,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { s3.listBuckets({}, () => { try { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() } catch (e) { done(e) diff --git a/packages/datadog-plugin-fs/test/index.spec.js b/packages/datadog-plugin-fs/test/index.spec.js index 8f52552697d..3d82ed75408 100644 --- a/packages/datadog-plugin-fs/test/index.spec.js +++ b/packages/datadog-plugin-fs/test/index.spec.js @@ -1886,7 +1886,7 @@ describe('Plugin', () => { const span = {} return tracer.scope().activate(span, () => { args.push((err) => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) if (err) { if (withError) withError(err) else done(err) @@ -1906,7 +1906,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return fs.promises[name].apply(fs.promises, args) .then(() => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) .catch((err) => { if (withError) withError(err) diff --git a/packages/datadog-plugin-grpc/test/client.spec.js b/packages/datadog-plugin-grpc/test/client.spec.js index c0f141e0aec..544f6c55012 100644 --- a/packages/datadog-plugin-grpc/test/client.spec.js +++ b/packages/datadog-plugin-grpc/test/client.spec.js @@ -519,7 +519,7 @@ describe('Plugin', () => { }).then(client => { tracer.scope().activate(span, () => { client.getUnary({ first: 'foobar' }, (err, response) => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done(err) }) }) @@ -539,7 +539,7 @@ describe('Plugin', () => { const call = client.getServerStream({ first: 'foobar' }) call.on('data', () => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() }) }) diff --git a/packages/datadog-plugin-http2/test/client.spec.js b/packages/datadog-plugin-http2/test/client.spec.js index f1efc2da796..2e833218b93 100644 --- a/packages/datadog-plugin-http2/test/client.spec.js +++ b/packages/datadog-plugin-http2/test/client.spec.js @@ -516,7 +516,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { const req = client.request({ ':path': '/user' }) req.on('response', (headers, flags) => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() }) diff --git a/packages/datadog-plugin-ioredis/test/index.spec.js b/packages/datadog-plugin-ioredis/test/index.spec.js index 57b388afa8c..d26943abc3a 100644 --- a/packages/datadog-plugin-ioredis/test/index.spec.js +++ b/packages/datadog-plugin-ioredis/test/index.spec.js @@ -60,7 +60,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, async () => { await redis.get('foo') - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) diff --git a/packages/datadog-plugin-iovalkey/test/index.spec.js b/packages/datadog-plugin-iovalkey/test/index.spec.js index 7a5bf6ef705..18ccbc9e763 100644 --- a/packages/datadog-plugin-iovalkey/test/index.spec.js +++ b/packages/datadog-plugin-iovalkey/test/index.spec.js @@ -61,7 +61,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, async () => { await valkey.get('foo') - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) diff --git a/packages/datadog-plugin-mongoose/test/index.spec.js b/packages/datadog-plugin-mongoose/test/index.spec.js index 372dcd2886b..4d81363a56d 100644 --- a/packages/datadog-plugin-mongoose/test/index.spec.js +++ b/packages/datadog-plugin-mongoose/test/index.spec.js @@ -80,7 +80,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return kitty.save().then(() => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) }) @@ -94,7 +94,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { Cat.find({ name: 'Zildjian' }).exec(() => { try { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() } catch (e) { done(e) @@ -114,7 +114,7 @@ describe('Plugin', () => { ) Cat.aggregate([{ $match: { name: 'Zildjian' } }]).exec(() => { try { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() } catch (e) { done(e) @@ -134,7 +134,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return promise.then(() => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) }) @@ -146,7 +146,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return Cat.find({ name: 'Zildjian' }).exec().then(() => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) }) @@ -158,7 +158,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return Cat.aggregate([{ $match: { name: 'Zildjian' } }]).exec().then(() => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) }) diff --git a/packages/datadog-plugin-oracledb/test/index.spec.js b/packages/datadog-plugin-oracledb/test/index.spec.js index 8bd08532085..897b4a997c9 100644 --- a/packages/datadog-plugin-oracledb/test/index.spec.js +++ b/packages/datadog-plugin-oracledb/test/index.spec.js @@ -108,7 +108,7 @@ describe('Plugin', () => { const span = {} return tracer.scope().activate(span, async () => { await connection.execute(dbQuery) - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) }) }) @@ -135,7 +135,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { connection.execute(dbQuery, () => { try { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) } catch (e) { return done(e) } diff --git a/packages/datadog-plugin-pg/test/index.spec.js b/packages/datadog-plugin-pg/test/index.spec.js index 9d21295b3f6..bfd4a14c89d 100644 --- a/packages/datadog-plugin-pg/test/index.spec.js +++ b/packages/datadog-plugin-pg/test/index.spec.js @@ -223,7 +223,7 @@ describe('Plugin', () => { const span = tracer.scope().active() client.query('SELECT $1::text as message', ['Hello World!'], () => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(tracer.scope().active()._span, span) done() }) diff --git a/packages/datadog-plugin-redis/test/client.spec.js b/packages/datadog-plugin-redis/test/client.spec.js index 7fca3ac1d7e..774b2dd9c25 100644 --- a/packages/datadog-plugin-redis/test/client.spec.js +++ b/packages/datadog-plugin-redis/test/client.spec.js @@ -139,7 +139,7 @@ describe('Plugin', () => { const span = {} tracer.scope().activate(span, () => { client.get('foo', () => { - assert.strictEqual(span.context().active(), span) + assert.strictEqual(span.context().active()._span, span) }) }) }) From 80a0c74926d134ec90b8bfb389b2b7f359c32d54 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 18:23:14 -0400 Subject: [PATCH 05/48] first batch of test fixes --- .../datadog-plugin-aerospike/test/index.spec.js | 2 +- packages/datadog-plugin-avsc/test/index.spec.js | 8 ++++---- .../datadog-plugin-child_process/src/index.js | 4 ---- .../test/index.spec.js | 8 +++++--- .../datadog-plugin-connect/test/index.spec.js | 2 +- packages/datadog-plugin-ws/test/index.spec.js | 5 +++-- .../dd-trace/src/opentracing/public/scope.js | 4 ++-- .../dd-trace/src/opentracing/public/span.js | 16 +++++++++++++--- packages/dd-trace/src/opentracing/tracer.js | 2 +- packages/dd-trace/test/proxy.spec.js | 17 ++++++++--------- 10 files changed, 38 insertions(+), 30 deletions(-) diff --git a/packages/datadog-plugin-aerospike/test/index.spec.js b/packages/datadog-plugin-aerospike/test/index.spec.js index ab7697ff49b..17fad22bc72 100644 --- a/packages/datadog-plugin-aerospike/test/index.spec.js +++ b/packages/datadog-plugin-aerospike/test/index.spec.js @@ -247,7 +247,7 @@ describe('Plugin', () => { aerospike.connect(config).then(client => { tracer.scope().activate(obj, () => { client.put(key, { i: 123 }, () => { - assert.strictEqual(tracer.scope().active(), obj) + assert.strictEqual(tracer.scope().active()._span, obj) client.close(false) done() }) diff --git a/packages/datadog-plugin-avsc/test/index.spec.js b/packages/datadog-plugin-avsc/test/index.spec.js index ae078f8b31a..f5bd5eb9390 100644 --- a/packages/datadog-plugin-avsc/test/index.spec.js +++ b/packages/datadog-plugin-avsc/test/index.spec.js @@ -81,7 +81,7 @@ describe('Plugin', () => { const buf = type.toBuffer({ name: 'Alyssa', favorite_number: 256, favorite_color: null }) fs.writeFileSync(filePath, buf) - assert.strictEqual(span._name, 'user.serialize') + assert.strictEqual(span.context()._name, 'user.serialize') assert.strictEqual(compareJson(BASIC_USER_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'avro') @@ -112,7 +112,7 @@ describe('Plugin', () => { }) fs.writeFileSync(filePath, buf) - assert.strictEqual(span._name, 'advanced_user.serialize') + assert.strictEqual(span.context()._name, 'advanced_user.serialize') assert.strictEqual(compareJson(ADVANCED_USER_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'avro') @@ -133,7 +133,7 @@ describe('Plugin', () => { tracer.trace('user.deserialize', span => { type.fromBuffer(buf) - assert.strictEqual(span._name, 'user.deserialize') + assert.strictEqual(span.context()._name, 'user.deserialize') assert.strictEqual(compareJson(BASIC_USER_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'avro') @@ -165,7 +165,7 @@ describe('Plugin', () => { tracer.trace('advanced_user.deserialize', span => { type.fromBuffer(buf) - assert.strictEqual(span._name, 'advanced_user.deserialize') + assert.strictEqual(span.context()._name, 'advanced_user.deserialize') assert.strictEqual(compareJson(ADVANCED_USER_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'avro') diff --git a/packages/datadog-plugin-child_process/src/index.js b/packages/datadog-plugin-child_process/src/index.js index 799a5ad52c5..b58d61a9758 100644 --- a/packages/datadog-plugin-child_process/src/index.js +++ b/packages/datadog-plugin-child_process/src/index.js @@ -31,10 +31,6 @@ class ChildProcessPlugin extends TracingPlugin { static id = 'child_process' static prefix = 'tracing:datadog:child_process:execution' - get tracer () { - return this._tracer - } - start (ctx) { const { command, shell } = ctx diff --git a/packages/datadog-plugin-child_process/test/index.spec.js b/packages/datadog-plugin-child_process/test/index.spec.js index 5e77c84bde5..d59e2ede4fc 100644 --- a/packages/datadog-plugin-child_process/test/index.spec.js +++ b/packages/datadog-plugin-child_process/test/index.spec.js @@ -545,10 +545,11 @@ describe('Child process plugin', () => { it('should maintain previous span after the execution', (done) => { const res = childProcess[methodName]('ls') const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan) + const expectedParentSpan = parentSpan?._span || parentSpan + assert.strictEqual(span, expectedParentSpan) if (async) { res.on('close', () => { - assert.strictEqual(span, parentSpan) + assert.strictEqual(span, expectedParentSpan) done() }) } else { @@ -560,7 +561,8 @@ describe('Child process plugin', () => { it('should maintain previous span in the callback', (done) => { childProcess[methodName]('ls', () => { const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan) + const expectedParentSpan = parentSpan?._span || parentSpan + assert.strictEqual(span, expectedParentSpan) done() }) }) diff --git a/packages/datadog-plugin-connect/test/index.spec.js b/packages/datadog-plugin-connect/test/index.spec.js index e7376268603..43b881d0f89 100644 --- a/packages/datadog-plugin-connect/test/index.spec.js +++ b/packages/datadog-plugin-connect/test/index.spec.js @@ -172,7 +172,7 @@ describe('Plugin', () => { let span app.use((req, res, next) => { - span = tracer.scope().active() + span = tracer.scope().active()._span sinon.spy(span, 'finish') diff --git a/packages/datadog-plugin-ws/test/index.spec.js b/packages/datadog-plugin-ws/test/index.spec.js index 563dcb4fcfc..6df13ee8251 100644 --- a/packages/datadog-plugin-ws/test/index.spec.js +++ b/packages/datadog-plugin-ws/test/index.spec.js @@ -643,8 +643,8 @@ describe('Plugin', () => { let didFindPointerLink = false await agent.assertSomeTraces(traces => { - const producerSpan = traces[0][0] - assert.strictEqual(producerSpan.name, 'websocket.send') + const producerSpan = traces.flat().find(s => s.name === 'websocket.send') + assert.ok(producerSpan, 'Should have a producer span') assert.strictEqual(producerSpan.service, 'ws-with-pointers') // Check for span links with span pointer attributes @@ -698,6 +698,7 @@ describe('Plugin', () => { const pointerLink = spanLinks.find(link => link.attributes && link.attributes['dd.kind'] === 'span-pointer' ) + assert.ok(pointerLink, 'Should have a span pointer link') assertObjectContains(pointerLink, { attributes: { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js index 7404f2078b4..5321fbb5788 100644 --- a/packages/dd-trace/src/opentracing/public/scope.js +++ b/packages/dd-trace/src/opentracing/public/scope.js @@ -13,11 +13,11 @@ class Scope { } activate (span, fn) { - return this._scope.activate(span, fn) + return this._scope.activate(span?._span || span, fn) } bind (fn, span) { - return this._scope.bind(fn, span) + return this._scope.bind(fn, span?._span || span) } } diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 4017a796a2b..d9ef2049d9a 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -5,6 +5,8 @@ const { SVC_SRC_KEY } = require('../../constants') const SERVICE_KEY = 'service' const SERVICE_NAME_KEY = 'service.name' +const cache = new WeakMap() + /** * This is a public wrapper of Span, this allows distinguishing internal usage from * external usage and acting accordingly. @@ -17,13 +19,21 @@ class PublicSpan { // This is needed for activate() get _store () { return this._span._store } - // This safely wraps a span, this is needed in cases i which active returns the same span resulting - // double wrapping + // A WeakMap cache ensures the same wrapper instance is returned for the same + // underlying span, so reference equality checks (===) in user code remain stable. static wrap (span) { if (span instanceof PublicSpan) { return span } - return new PublicSpan(span) + const cached = cache.get(span) + if (cached !== undefined) { + return cached + } + const wrapper = new PublicSpan(span) + try { + cache.set(span, wrapper) + } catch {} + return wrapper } setTag (key, value) { diff --git a/packages/dd-trace/src/opentracing/tracer.js b/packages/dd-trace/src/opentracing/tracer.js index 3c4d5aa1183..2dfa82febd5 100644 --- a/packages/dd-trace/src/opentracing/tracer.js +++ b/packages/dd-trace/src/opentracing/tracer.js @@ -82,7 +82,7 @@ class DatadogTracer { } inject (context, format, carrier) { - if (context instanceof Span) { + if (context instanceof Span || context instanceof PublicSpan) { context = context.context() } diff --git a/packages/dd-trace/test/proxy.spec.js b/packages/dd-trace/test/proxy.spec.js index 3d9650aaa5d..b024ec90299 100644 --- a/packages/dd-trace/test/proxy.spec.js +++ b/packages/dd-trace/test/proxy.spec.js @@ -5,7 +5,6 @@ const assert = require('node:assert/strict') const { describe, it, beforeEach, afterEach } = require('mocha') const sinon = require('sinon') const proxyquire = require('proxyquire') -const PublicSpan = require('../src/opentracing/public/span') require('./setup/core') describe('TracerProxy', () => { @@ -826,9 +825,9 @@ describe('TracerProxy', () => { activate: sinon.stub().callsFake((span, fn) => fn()), bind: sinon.stub(), } - const internalSpan = new PublicSpan({ + const internalSpan = { context: sinon.stub(), - }) + } noop.scope.returns(scope) noop.startSpan.returns(internalSpan) @@ -848,9 +847,9 @@ describe('TracerProxy', () => { activate: sinon.stub(), bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), } - const internalSpan = new PublicSpan({ + const internalSpan = { context: sinon.stub(), - }) + } const fn = sinon.stub() noop.scope.returns(scope) @@ -1201,9 +1200,9 @@ describe('TracerProxy', () => { activate: sinon.stub().callsFake((span, fn) => fn()), bind: sinon.stub(), } - const internalSpan = new PublicSpan({ + const internalSpan = { context: sinon.stub(), - }) + } tracer.scope.returns(scope) tracer.startSpan.returns(internalSpan) @@ -1222,9 +1221,9 @@ describe('TracerProxy', () => { activate: sinon.stub(), bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), } - const internalSpan = new PublicSpan({ + const internalSpan = { context: sinon.stub(), - }) + } const fn = sinon.stub() tracer.scope.returns(scope) From 60973e03ce1050b34e1ae44f102490cabe93d8c7 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 18:34:39 -0400 Subject: [PATCH 06/48] more test fixes --- .../datadog-plugin-express/test/index.spec.js | 2 +- .../datadog-plugin-koa/test/index.spec.js | 2 +- packages/datadog-plugin-pg/test/index.spec.js | 2 +- .../test/index.spec.js | 26 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 083e75ed596..35982120861 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -628,7 +628,7 @@ describe('Plugin', () => { let span app.use((req, res, next) => { - span = tracer.scope().active() + span = tracer.scope().active()._span sinon.spy(span, 'finish') diff --git a/packages/datadog-plugin-koa/test/index.spec.js b/packages/datadog-plugin-koa/test/index.spec.js index 34201f5d33e..673382b73eb 100644 --- a/packages/datadog-plugin-koa/test/index.spec.js +++ b/packages/datadog-plugin-koa/test/index.spec.js @@ -192,7 +192,7 @@ describe('Plugin', () => { let childSpan app.use((ctx, next) => { - parentSpan = tracer.scope().active() + parentSpan = tracer.scope().active()._span sinon.spy(parentSpan, 'finish') diff --git a/packages/datadog-plugin-pg/test/index.spec.js b/packages/datadog-plugin-pg/test/index.spec.js index bfd4a14c89d..9d21295b3f6 100644 --- a/packages/datadog-plugin-pg/test/index.spec.js +++ b/packages/datadog-plugin-pg/test/index.spec.js @@ -223,7 +223,7 @@ describe('Plugin', () => { const span = tracer.scope().active() client.query('SELECT $1::text as message', ['Hello World!'], () => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() }) diff --git a/packages/datadog-plugin-protobufjs/test/index.spec.js b/packages/datadog-plugin-protobufjs/test/index.spec.js index ca8f254bbd5..67dc89cfe19 100644 --- a/packages/datadog-plugin-protobufjs/test/index.spec.js +++ b/packages/datadog-plugin-protobufjs/test/index.spec.js @@ -73,7 +73,7 @@ describe('Plugin', () => { tracer.trace('other_message.serialize', span => { loadedMessages.OtherMessage.type.encode(loadedMessages.OtherMessage.instance).finish() - assert.strictEqual(span._name, 'other_message.serialize') + assert.strictEqual(span.context()._name, 'other_message.serialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -89,7 +89,7 @@ describe('Plugin', () => { tracer.trace('other_message.serialize', span => { loadedMessages.OtherMessage.type.encode(loadedMessages.OtherMessage.instance).finish() - assert.strictEqual(span._name, 'other_message.serialize') + assert.strictEqual(span.context()._name, 'other_message.serialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -107,7 +107,7 @@ describe('Plugin', () => { tracer.trace('message_pb2.serialize', span => { loadedMessages.MyMessage.type.encode(loadedMessages.MyMessage.instance).finish() - assert.strictEqual(span._name, 'message_pb2.serialize') + assert.strictEqual(span.context()._name, 'message_pb2.serialize') assert.strictEqual(compareJson(MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -124,7 +124,7 @@ describe('Plugin', () => { tracer.trace('all_types.serialize', span => { loadedMessages.MainMessage.type.encode(loadedMessages.MainMessage.instance).finish() - assert.strictEqual(span._name, 'all_types.serialize') + assert.strictEqual(span.context()._name, 'all_types.serialize') assert.strictEqual(compareJson(ALL_TYPES_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -143,7 +143,7 @@ describe('Plugin', () => { tracer.trace('other_message.deserialize', span => { loadedMessages.OtherMessage.type.decode(bytes) - assert.strictEqual(span._name, 'other_message.deserialize') + assert.strictEqual(span.context()._name, 'other_message.deserialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -162,7 +162,7 @@ describe('Plugin', () => { tracer.trace('my_message.deserialize', span => { loadedMessages.MyMessage.type.decode(bytes) - assert.strictEqual(span._name, 'my_message.deserialize') + assert.strictEqual(span.context()._name, 'my_message.deserialize') assert.strictEqual(compareJson(MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -181,7 +181,7 @@ describe('Plugin', () => { tracer.trace('all_types.deserialize', span => { loadedMessages.MainMessage.type.decode(bytes) - assert.strictEqual(span._name, 'all_types.deserialize') + assert.strictEqual(span.context()._name, 'all_types.deserialize') assert.strictEqual(compareJson(ALL_TYPES_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -206,7 +206,7 @@ describe('Plugin', () => { tracer.trace('other_message.deserialize', span => { OtherMessage.decode(bytes) - assert.strictEqual(span._name, 'other_message.deserialize') + assert.strictEqual(span.context()._name, 'other_message.deserialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -230,7 +230,7 @@ describe('Plugin', () => { tracer.trace('other_message.deserialize', span => { OtherMessage.decodeDelimited(bytes) - assert.strictEqual(span._name, 'other_message.deserialize') + assert.strictEqual(span.context()._name, 'other_message.deserialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -256,7 +256,7 @@ describe('Plugin', () => { tracer.trace('other_message.deserialize', span => { OtherMessage.decodeDelimited(bytes) - assert.strictEqual(span._name, 'other_message.deserialize') + assert.strictEqual(span.context()._name, 'other_message.deserialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -282,7 +282,7 @@ describe('Plugin', () => { tracer.trace('other_message.deserialize', span => { OtherMessage.decodeDelimited(bytes) - assert.strictEqual(span._name, 'other_message.deserialize') + assert.strictEqual(span.context()._name, 'other_message.deserialize') assert.strictEqual(compareJson(OTHER_MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -315,7 +315,7 @@ describe('Plugin', () => { tracer.trace('message_pb2.serialize', span => { loadedMessages.MyMessage.type.encode(loadedMessages.MyMessage.instance).finish() - assert.strictEqual(span._name, 'message_pb2.serialize') + assert.strictEqual(span.context()._name, 'message_pb2.serialize') assert.strictEqual(compareJson(MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') @@ -332,7 +332,7 @@ describe('Plugin', () => { tracer.trace('message_pb2.serialize', span => { loadedMessages.MyMessage.type.encode(loadedMessages.MyMessage.instance).finish() - assert.strictEqual(span._name, 'message_pb2.serialize') + assert.strictEqual(span.context()._name, 'message_pb2.serialize') assert.strictEqual(compareJson(MESSAGE_SCHEMA_DEF, span), true) assert.strictEqual(span.context()._tags[SCHEMA_TYPE], 'protobuf') From db7026a50f9924f3360bfd8531863ca965a983a5 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 18:38:49 -0400 Subject: [PATCH 07/48] fix koa --- packages/datadog-plugin-koa/test/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-koa/test/index.spec.js b/packages/datadog-plugin-koa/test/index.spec.js index 673382b73eb..4952a44fb24 100644 --- a/packages/datadog-plugin-koa/test/index.spec.js +++ b/packages/datadog-plugin-koa/test/index.spec.js @@ -213,7 +213,7 @@ describe('Plugin', () => { }) app.use((ctx, next) => { - childSpan = tracer.scope().active() + childSpan = tracer.scope().active()._span sinon.spy(childSpan, 'finish') From 075344be6054a3bd6eb306b25b997e648baff501 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 21:14:01 -0400 Subject: [PATCH 08/48] fixed proxy and some failing tests --- .../test/index.spec.js | 84 +++++++++++++------ .../test/index.spec.js | 2 +- .../datadog-plugin-rhea/test/index.spec.js | 2 +- packages/dd-trace/src/noop/proxy.js | 12 +-- .../dd-trace/test/llmobs/sdk/index.spec.js | 14 ++-- 5 files changed, 75 insertions(+), 39 deletions(-) diff --git a/packages/datadog-plugin-dd-trace-api/test/index.spec.js b/packages/datadog-plugin-dd-trace-api/test/index.spec.js index 7cda39c13de..e85a163f630 100644 --- a/packages/datadog-plugin-dd-trace-api/test/index.spec.js +++ b/packages/datadog-plugin-dd-trace-api/test/index.spec.js @@ -92,40 +92,48 @@ describe('Plugin', () => { describe('scope:active', () => { it('should call underlying api', () => { scope = tracer.scope() - sinon.spy(scope, 'active') + const internalScope = scope._scope || scope + sinon.spy(internalScope, 'active') testChannel({ name: 'scope:active', - fn: scope.active, + fn: internalScope.active, self: dummyScope, ret: null, + thisValue: internalScope, }) - scope.active.restore() + internalScope.active.restore() }) }) describe('scope:activate', () => { it('should call underlying api', () => { scope = tracer.scope() - sinon.spy(scope, 'activate') + const internalScope = scope._scope || scope + sinon.spy(internalScope, 'activate') testChannel({ name: 'scope:activate', - fn: scope.activate, + fn: internalScope.activate, self: dummyScope, + args: [undefined, undefined], + thisValue: internalScope, }) - scope.activate.restore() + internalScope.activate.restore() }) }) describe('scope:bind', () => { it('should call underlying api', () => { scope = tracer.scope() - sinon.spy(scope, 'bind') + const internalScope = scope._scope || scope + sinon.spy(internalScope, 'bind') testChannel({ name: 'scope:bind', - fn: scope.bind, + fn: internalScope.bind, self: dummyScope, + args: [undefined, undefined], + thisValue: internalScope, }) - scope.bind.restore() + internalScope.bind.restore() }) }) }) @@ -135,6 +143,8 @@ describe('Plugin', () => { let dummySpanContext let span let spanContext + let internalSpan + let internalSpanContext it('should call underlying api', () => { dummySpan = {} @@ -144,7 +154,7 @@ describe('Plugin', () => { ret: dummySpan, }) span = tracer.startSpan.getCall(0).returnValue - sinon.spy(span) + internalSpan = span._span || span }) describe('span:context', () => { @@ -154,25 +164,30 @@ describe('Plugin', () => { it('should call underlying api', () => { dummySpanContext = {} + sinon.spy(internalSpan, 'context') testChannel({ name: 'span:context', - fn: span.context, + fn: internalSpan.context, self: dummySpan, ret: dummySpanContext, + thisValue: internalSpan, }) - spanContext = span.context.getCall(0).returnValue - sinon.stub(spanContext, 'toTraceId').callsFake(() => traceId) - sinon.stub(spanContext, 'toSpanId').callsFake(() => spanId) - sinon.stub(spanContext, 'toTraceparent').callsFake(() => traceparent) + spanContext = internalSpan.context.getCall(0).returnValue + internalSpanContext = spanContext._spanContext || spanContext + sinon.stub(internalSpanContext, 'toTraceId').callsFake(() => traceId) + sinon.stub(internalSpanContext, 'toSpanId').callsFake(() => spanId) + sinon.stub(internalSpanContext, 'toTraceparent').callsFake(() => traceparent) + internalSpan.context.restore() }) describe('context:toTraceId', () => { it('should call underlying api', () => { testChannel({ name: 'context:toTraceId', - fn: spanContext.toTraceId, + fn: internalSpanContext.toTraceId, self: dummySpanContext, ret: traceId, + thisValue: internalSpanContext, }) }) }) @@ -181,9 +196,10 @@ describe('Plugin', () => { it('should call underlying api', () => { testChannel({ name: 'context:toSpanId', - fn: spanContext.toSpanId, + fn: internalSpanContext.toSpanId, self: dummySpanContext, ret: spanId, + thisValue: internalSpanContext, }) }) }) @@ -192,9 +208,10 @@ describe('Plugin', () => { it('should call underlying api', () => { testChannel({ name: 'context:toTraceparent', - fn: spanContext.toTraceparent, + fn: internalSpanContext.toTraceparent, self: dummySpanContext, ret: traceparent, + thisValue: internalSpanContext, }) }) }) @@ -202,45 +219,60 @@ describe('Plugin', () => { describe('span:setTag', () => { it('should call underlying api', () => { + sinon.spy(internalSpan, 'setTag') testChannel({ name: 'span:setTag', - fn: span.setTag, + fn: internalSpan.setTag, self: dummySpan, ret: dummySpan, + args: ['test.tag', 'test.value'], + thisValue: internalSpan, }) + internalSpan.setTag.restore() }) }) describe('span:addTags', () => { it('should call underlying api', () => { + sinon.spy(internalSpan, 'addTags') testChannel({ name: 'span:addTags', - fn: span.addTags, + fn: internalSpan.addTags, self: dummySpan, ret: dummySpan, + args: [{ 'test.tag': 'test.value' }], + thisValue: internalSpan, }) + internalSpan.addTags.restore() }) }) describe('span:finish', () => { it('should call underlying api', () => { + sinon.spy(internalSpan, 'finish') testChannel({ name: 'span:finish', - fn: span.finish, + fn: internalSpan.finish, self: dummySpan, + args: [undefined], + thisValue: internalSpan, }) + internalSpan.finish.restore() }) }) describe('span:addLink', () => { it('should call underlying api', () => { + sinon.spy(internalSpan, 'addLink') testChannel({ name: 'span:addLink', - fn: span.addLink, + fn: internalSpan.addLink, self: dummySpan, ret: dummySpan, args: [dummySpanContext], + thisValue: internalSpan, }) + internalSpan.addLink.restore() }) }) }) @@ -297,7 +329,7 @@ describe('Plugin', () => { }) } - function testChannel ({ name, fn, self = dummyTracer, ret, args = [], proxy }) { + function testChannel ({ name, fn, self = dummyTracer, ret, args = [], proxy, thisValue }) { testedChannels.add('datadog-api:v1:' + name) const ch = dc.channel('datadog-api:v1:' + name) if (proxy === undefined) { @@ -309,7 +341,11 @@ describe('Plugin', () => { throw payload.ret.error } assert.strictEqual(payload.ret.value, ret) - sinon.assert.calledOnceWithExactly(fn, ...args) + sinon.assert.calledOnce(fn) + assert.deepStrictEqual(fn.args, [args]) + if (typeof thisValue !== 'undefined') { + assert.strictEqual(fn.thisValues[0], thisValue) + } } }) }) diff --git a/packages/datadog-plugin-google-cloud-pubsub/test/index.spec.js b/packages/datadog-plugin-google-cloud-pubsub/test/index.spec.js index 7aaf5f85bbc..09209824359 100644 --- a/packages/datadog-plugin-google-cloud-pubsub/test/index.spec.js +++ b/packages/datadog-plugin-google-cloud-pubsub/test/index.spec.js @@ -228,7 +228,7 @@ describe('Plugin', () => { sub.on('message', msg => { const activeSpan = tracer.scope().active() if (activeSpan) { - const receiverSpanContext = activeSpan._spanContext + const receiverSpanContext = activeSpan.context() assert.ok(typeof receiverSpanContext._parentId === 'object' && receiverSpanContext._parentId !== null) } msg.ack() diff --git a/packages/datadog-plugin-rhea/test/index.spec.js b/packages/datadog-plugin-rhea/test/index.spec.js index 92a95ef3e6b..a098a4b1c44 100644 --- a/packages/datadog-plugin-rhea/test/index.spec.js +++ b/packages/datadog-plugin-rhea/test/index.spec.js @@ -243,7 +243,7 @@ describe('Plugin', () => { it('should extract the span context', done => { container.once('message', msg => { const span = tracer.scope().active() - assert.notStrictEqual(span._spanContext._parentId, null) + assert.notStrictEqual(span.context()._parentId, null) done() }) context.sender.send({ body: 'Hello World!' }) diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 72b907ab6af..f794635971d 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -61,9 +61,9 @@ class NoopProxy { } } - const callback = (span, done) => { - return fn(PublicSpan.wrap(span), done) - } + const callback = fn.length > 1 + ? (span, done) => fn(PublicSpan.wrap(span), done) + : span => fn(PublicSpan.wrap(span)) return this._tracer.trace(name, options, callback) } @@ -82,9 +82,9 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - const callback = (span, done) => { - return fn(PublicSpan.wrap(span), done) - } + const callback = fn.length > 1 + ? (span, done) => fn(PublicSpan.wrap(span), done) + : span => fn(PublicSpan.wrap(span)) return this._tracer.wrap(name, options, callback) } diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index d2c8ce7a586..1a9f72e31fa 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -393,7 +393,7 @@ describe('sdk', () => { it('wraps a function', () => { let span const fn = llmobs.wrap({ kind: 'workflow' }, () => { - span = tracer.scope().active() + span = tracer.scope().active()._span sinon.spy(span, 'finish') }) @@ -407,7 +407,7 @@ describe('sdk', () => { let next const fn = llmobs.wrap({ kind: 'workflow' }, (_next) => { - span = tracer.scope().active() + span = tracer.scope().active()._span sinon.spy(span, 'finish') next = _next }) @@ -708,7 +708,7 @@ describe('sdk', () => { function outerLLMObs () { outerLLMObsSpan = llmobs._active() - assert.strictEqual(outerLLMObsSpan, tracer.scope().active()) + assert.strictEqual(outerLLMObsSpan, tracer.scope().active()._span) apmWrapped() } @@ -718,7 +718,7 @@ describe('sdk', () => { } function innerLLMObs () { innerLLMObsSpan = llmobs._active() - assert.strictEqual(innerLLMObsSpan, tracer.scope().active()) + assert.strictEqual(innerLLMObsSpan, tracer.scope().active()._span) assert.strictEqual( LLMObsTagger.tagMap.get(innerLLMObsSpan)['_ml_obs.llmobs_parent_id'], outerLLMObsSpan.context().toSpanId() @@ -766,12 +766,12 @@ describe('sdk', () => { function outer () { outerSpan = llmobs._active() wrappedInner1(() => {}) - assert.strictEqual(outerSpan, tracer.scope().active()) + assert.strictEqual(outerSpan, tracer.scope().active()._span) wrappedInner2() } function inner1 (cb) { - const inner = tracer.scope().active() + const inner = tracer.scope().active()._span assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], @@ -781,7 +781,7 @@ describe('sdk', () => { } function inner2 () { - const inner = tracer.scope().active() + const inner = tracer.scope().active()._span assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], From 8bf7c6490856bb095a36cb09bb5c246501e747cd Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 21:25:18 -0400 Subject: [PATCH 09/48] fixed aiguard --- packages/dd-trace/src/aiguard/sdk.js | 5 +++-- packages/dd-trace/src/noop/proxy.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/dd-trace/src/aiguard/sdk.js b/packages/dd-trace/src/aiguard/sdk.js index 64886ba092a..20a6aa7a108 100644 --- a/packages/dd-trace/src/aiguard/sdk.js +++ b/packages/dd-trace/src/aiguard/sdk.js @@ -140,6 +140,7 @@ class AIGuard extends NoopAIGuard { } const { block = true } = opts ?? {} return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => { + const internalSpan = span?._span || span const last = messages[messages.length - 1] const target = this.#isToolCall(last) ? 'tool' : 'prompt' span.setTag(AI_GUARD_TARGET_TAG_KEY, target) @@ -152,10 +153,10 @@ class AIGuard extends NoopAIGuard { const metaStruct = { messages: this.#buildMessagesForMetaStruct(messages), } - span.meta_struct = { + internalSpan.meta_struct = { [AI_GUARD_META_STRUCT_KEY]: metaStruct, } - const rootSpan = span.context()?._trace?.started?.[0] + const rootSpan = internalSpan.context()?._trace?.started?.[0] if (rootSpan) { // keepTrace must be called before executeRequest so the sampling decision // is propagated correctly to outgoing HTTP client calls. diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index f794635971d..93dd55a9657 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -62,8 +62,8 @@ class NoopProxy { } const callback = fn.length > 1 - ? (span, done) => fn(PublicSpan.wrap(span), done) - : span => fn(PublicSpan.wrap(span)) + ? function (span, done) { return fn(PublicSpan.wrap(span), done) } + : function (span) { return fn(PublicSpan.wrap(span)) } return this._tracer.trace(name, options, callback) } @@ -83,8 +83,8 @@ class NoopProxy { } } const callback = fn.length > 1 - ? (span, done) => fn(PublicSpan.wrap(span), done) - : span => fn(PublicSpan.wrap(span)) + ? function (span, done) { return fn(PublicSpan.wrap(span), done) } + : function (span) { return fn(PublicSpan.wrap(span)) } return this._tracer.wrap(name, options, callback) } From 7d394da18c86d3b92c47f0ceadb384948d5c1871 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 21:53:31 -0400 Subject: [PATCH 10/48] removed PublicSpan from wrap --- packages/dd-trace/src/noop/proxy.js | 6 +--- packages/dd-trace/test/proxy.spec.js | 49 +++------------------------- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 93dd55a9657..69fce7caf2b 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -82,11 +82,7 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - const callback = fn.length > 1 - ? function (span, done) { return fn(PublicSpan.wrap(span), done) } - : function (span) { return fn(PublicSpan.wrap(span)) } - - return this._tracer.wrap(name, options, callback) + return this._tracer.wrap(name, options, fn) } setUrl () { diff --git a/packages/dd-trace/test/proxy.spec.js b/packages/dd-trace/test/proxy.spec.js index b024ec90299..72de44dcecc 100644 --- a/packages/dd-trace/test/proxy.spec.js +++ b/packages/dd-trace/test/proxy.spec.js @@ -669,7 +669,7 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', 'b', callback) - sinon.assert.calledWith(noop.wrap, 'a', 'b', sinon.match.func) + sinon.assert.calledWith(noop.wrap, 'a', 'b', callback) assert.strictEqual(returnValue, 'fn') }) @@ -677,37 +677,10 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', callback) - sinon.assert.calledWith(noop.wrap, 'a', {}, sinon.match.func) + sinon.assert.calledWith(noop.wrap, 'a', {}, callback) assert.strictEqual(returnValue, 'fn') }) - it('should add _dd.svc_src to tags when a service override is provided through options', () => { - const callback = () => 'test' - const returnValue = proxy.wrap('a', { - service: 'custom-service', - }, callback) - - sinon.assert.calledWith(noop.wrap, 'a', { - service: 'custom-service', - tags: { - '_dd.svc_src': 'm', - }, - }, sinon.match.func) - assert.notStrictEqual(noop.wrap.firstCall.args[2], callback) - assert.strictEqual(returnValue, 'fn') - }) - - it('should patch the wrap callback span setTag when a service override is provided', () => { - const spanStub = { setTag: sinon.spy() } - const setTagSpy = spanStub.setTag - noop.wrap.callsFake((name, options, callback) => callback(spanStub)) - - proxy.wrap('a', (span, done) => span.setTag('service', 'b')) - assert.strictEqual(setTagSpy.callCount, 2) - sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') - }) - it('should ignore calls without an invalid callback', () => { const returnValue = proxy.wrap('a', 'b') @@ -1051,7 +1024,7 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', 'b', callback) - sinon.assert.calledWith(tracer.wrap, 'a', 'b', sinon.match.func) + sinon.assert.calledWith(tracer.wrap, 'a', 'b', callback) assert.strictEqual(returnValue, 'fn') }) @@ -1066,27 +1039,15 @@ describe('TracerProxy', () => { tags: { '_dd.svc_src': 'm', }, - }, sinon.match.func) - assert.notStrictEqual(tracer.wrap.firstCall.args[2], callback) + }, callback) assert.strictEqual(returnValue, 'fn') }) - it('should patch the wrap callback span setTag when a service override is provided', () => { - const spanStub = { setTag: sinon.spy() } - const setTagSpy = spanStub.setTag - tracer.wrap.callsFake((name, options, callback) => callback(spanStub)) - - proxy.wrap('a', (span, done) => span.setTag('service', 'b')) - assert.strictEqual(setTagSpy.callCount, 2) - sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') - }) - it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.wrap('a', callback) - sinon.assert.calledWith(tracer.wrap, 'a', {}, sinon.match.func) + sinon.assert.calledWith(tracer.wrap, 'a', {}, callback) assert.strictEqual(returnValue, 'fn') }) }) From 7aa71b0acff5eb68738bf4b338869226b220c613 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 29 Mar 2026 23:03:42 -0400 Subject: [PATCH 11/48] fix cypress --- packages/datadog-plugin-cypress/src/cypress-plugin.js | 4 ++-- packages/dd-trace/src/noop/proxy.js | 2 +- packages/dd-trace/src/opentracing/public/span.js | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 1889ede16f0..5be6ac43642 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -1035,11 +1035,11 @@ class CypressPlugin { if (isQuarantinedFromSupport) { this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true') } - + const activeSpan = this.activeTestSpan._span || this.activeTestSpan const finishedTest = { testName, testStatus, - finishTime: this.activeTestSpan._getTime(), // we store the finish time here + finishTime: activeSpan._getTime(), // we store the finish time here testSpan: this.activeTestSpan, isEfdRetry, isAttemptToFix, diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 69fce7caf2b..d92d9004f86 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -6,8 +6,8 @@ const NoopFlaggingProvider = require('../openfeature/noop') const NoopAIGuardSDK = require('../aiguard/noop') const PublicSpan = require('../opentracing/public/span') const PublicScope = require('../opentracing/public/scope') -const NoopDogStatsDClient = require('./dogstatsd') const { SVC_SRC_KEY } = require('../../src/constants') +const NoopDogStatsDClient = require('./dogstatsd') const NoopTracer = require('./tracer') const noop = new NoopTracer() diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index d9ef2049d9a..fb747ca7675 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -16,9 +16,6 @@ class PublicSpan { this._span = span } - // This is needed for activate() - get _store () { return this._span._store } - // A WeakMap cache ensures the same wrapper instance is returned for the same // underlying span, so reference equality checks (===) in user code remain stable. static wrap (span) { @@ -71,7 +68,7 @@ for (const method of [ 'addSpanPointer', 'addEvent', 'finish', - 'toString' + 'toString', ]) { PublicSpan.prototype[method] = function (...args) { const result = this._span[method](...args) From 65178a280850c868a08ec97c2572d1ad9314f67e Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 10:28:08 -0400 Subject: [PATCH 12/48] fix context manager --- packages/dd-trace/src/opentelemetry/context_manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dd-trace/src/opentelemetry/context_manager.js b/packages/dd-trace/src/opentelemetry/context_manager.js index 3537c331fe2..e0999454f45 100644 --- a/packages/dd-trace/src/opentelemetry/context_manager.js +++ b/packages/dd-trace/src/opentelemetry/context_manager.js @@ -16,7 +16,8 @@ class ContextManager { active () { const store = this._store.getStore() const baseContext = store || ROOT_CONTEXT - const activeSpan = tracer.scope().active() + const publicSpan = tracer.scope().active() + const activeSpan = publicSpan?._span || publicSpan const storedSpan = store ? trace.getSpan(store) : null From baa045c306b58c60eadd5b73a352a75d8234d568 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 10:34:10 -0400 Subject: [PATCH 13/48] fix tracer test --- packages/dd-trace/test/opentelemetry/tracer.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/test/opentelemetry/tracer.spec.js b/packages/dd-trace/test/opentelemetry/tracer.spec.js index b6d49b9312c..10626fc5e31 100644 --- a/packages/dd-trace/test/opentelemetry/tracer.spec.js +++ b/packages/dd-trace/test/opentelemetry/tracer.spec.js @@ -125,7 +125,7 @@ describe('OTel Tracer', () => { otelTracer.startActiveSpan('name', (span) => { assert.ok(span instanceof Span) - assert.strictEqual(span._ddSpan, tracer.scope().active()) + assert.strictEqual(span._ddSpan, tracer.scope().active()._span) }) }) From 790cbe50b2d1b28653ccfa10a4ff7f7cace4383c Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 15:38:24 -0400 Subject: [PATCH 14/48] removed scope and trace wrapping --- packages/dd-trace/src/noop/proxy.js | 10 +- .../dd-trace/src/opentracing/public/scope.js | 24 --- .../dd-trace/src/opentracing/public/span.js | 19 +- packages/dd-trace/src/scope.js | 8 +- packages/dd-trace/src/tracer.js | 3 +- packages/dd-trace/test/proxy.spec.js | 195 ++---------------- packages/dd-trace/test/scope.spec.js | 35 +++- packages/dd-trace/test/tracer.spec.js | 24 +-- 8 files changed, 69 insertions(+), 249 deletions(-) delete mode 100644 packages/dd-trace/src/opentracing/public/scope.js diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index d92d9004f86..54f705df761 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -5,7 +5,6 @@ const NoopLLMObsSDK = require('../llmobs/noop') const NoopFlaggingProvider = require('../openfeature/noop') const NoopAIGuardSDK = require('../aiguard/noop') const PublicSpan = require('../opentracing/public/span') -const PublicScope = require('../opentracing/public/scope') const { SVC_SRC_KEY } = require('../../src/constants') const NoopDogStatsDClient = require('./dogstatsd') const NoopTracer = require('./tracer') @@ -61,10 +60,7 @@ class NoopProxy { } } - const callback = fn.length > 1 - ? function (span, done) { return fn(PublicSpan.wrap(span), done) } - : function (span) { return fn(PublicSpan.wrap(span)) } - return this._tracer.trace(name, options, callback) + return this._tracer.trace(name, options, fn) } wrap (name, options, fn) { @@ -97,7 +93,7 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - return PublicSpan.wrap(this._tracer.startSpan.apply(this._tracer, arguments)) + return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) } inject () { @@ -109,7 +105,7 @@ class NoopProxy { } scope () { - return new PublicScope(this._tracer.scope.apply(this._tracer, arguments)) + return this._tracer.scope.apply(this._tracer, arguments) } getRumData () { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js deleted file mode 100644 index 5321fbb5788..00000000000 --- a/packages/dd-trace/src/opentracing/public/scope.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const PublicSpan = require('./span') - -class Scope { - constructor (scope) { - this._scope = scope - } - - active () { - const span = this._scope.active() - return span ? PublicSpan.wrap(span) : null - } - - activate (span, fn) { - return this._scope.activate(span?._span || span, fn) - } - - bind (fn, span) { - return this._scope.bind(fn, span?._span || span) - } -} - -module.exports = Scope diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index fb747ca7675..574fbe892a8 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -5,32 +5,25 @@ const { SVC_SRC_KEY } = require('../../constants') const SERVICE_KEY = 'service' const SERVICE_NAME_KEY = 'service.name' -const cache = new WeakMap() - /** * This is a public wrapper of Span, this allows distinguishing internal usage from * external usage and acting accordingly. */ class PublicSpan { - constructor (span) { - this._span = span - } - // A WeakMap cache ensures the same wrapper instance is returned for the same // underlying span, so reference equality checks (===) in user code remain stable. - static wrap (span) { + static #cache = new WeakMap() + + constructor (span) { if (span instanceof PublicSpan) { return span } - const cached = cache.get(span) + const cached = PublicSpan.#cache.get(span) if (cached !== undefined) { return cached } - const wrapper = new PublicSpan(span) - try { - cache.set(span, wrapper) - } catch {} - return wrapper + this._span = span + PublicSpan.#cache.set(span, this) } setTag (key, value) { diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index cc3fd1a86c1..a53e629fefb 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -1,6 +1,7 @@ 'use strict' const { storage } = require('../../datadog-core') +const PublicSpan = require('./opentracing/public/span') // TODO: refactor bind to use shimmer once the new internal tracer lands @@ -9,13 +10,16 @@ const originals = new WeakMap() class Scope { active () { const store = storage('legacy').getStore() + const span = (store && store.span) || null - return (store && store.span) || null + return span ? new PublicSpan(span) : null } activate (span, callback) { if (typeof callback !== 'function') return callback + span = span?._span || span + const oldStore = storage('legacy').getStore() const newStore = span ? storage('legacy').getStore(span._store) : oldStore @@ -38,7 +42,7 @@ class Scope { if (typeof fn !== 'function') return fn const scope = this - const spanOrActive = this._spanOrActive(span) + const spanOrActive = this._spanOrActive(span?._span || span) const bound = function () { return scope.activate(spanOrActive, () => { diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index 556d18652d6..4b73445cd99 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -5,6 +5,7 @@ const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/c const { flushStartupLogs } = require('../../datadog-instrumentations/src/helpers/check-require-cache') const Tracer = require('./opentracing/tracer') const Scope = require('./scope') +const PublicSpan = require('./opentracing/public/span') const { isError } = require('./util') const { setStartupLogConfig } = require('./startup-log') const { DataStreamsCheckpointer, DataStreamsManager, DataStreamsProcessor } = require('./datastreams') @@ -60,7 +61,7 @@ class DatadogTracer extends Tracer { trace (name, options, fn) { options = { childOf: this.scope().active(), ...options } - const span = this.startSpan(name, options) + const span = PublicSpan.wrap(this.startSpan(name, options)) addTags(span, options) diff --git a/packages/dd-trace/test/proxy.spec.js b/packages/dd-trace/test/proxy.spec.js index 72de44dcecc..013008e98e9 100644 --- a/packages/dd-trace/test/proxy.spec.js +++ b/packages/dd-trace/test/proxy.spec.js @@ -65,7 +65,7 @@ describe('TracerProxy', () => { use: sinon.stub().returns('tracer'), trace: sinon.stub().returns('test'), wrap: sinon.stub().returns('fn'), - startSpan: sinon.stub().returns('span'), + startSpan: sinon.stub().returns({ id: 'span' }), inject: sinon.stub().returns('tracer'), extract: sinon.stub().returns('spanContext'), setUrl: sinon.stub(), @@ -76,7 +76,7 @@ describe('TracerProxy', () => { use: sinon.stub().returns('tracer'), trace: sinon.stub().returns('test'), wrap: sinon.stub().returns('fn'), - startSpan: sinon.stub().returns('span'), + startSpan: sinon.stub().returns({ id: 'span' }), inject: sinon.stub().returns('noop'), extract: sinon.stub().returns('spanContext'), setUrl: sinon.stub(), @@ -622,14 +622,14 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.trace('a', 'b', callback) - sinon.assert.calledWith(noop.trace, 'a', 'b', sinon.match.func) + sinon.assert.calledWith(noop.trace, 'a', 'b', callback) assert.strictEqual(returnValue, 'test') }) it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.trace('a', callback) - sinon.assert.calledWith(noop.trace, 'a', {}, sinon.match.func) + sinon.assert.calledWith(noop.trace, 'a', {}, callback) assert.strictEqual(returnValue, 'test') }) @@ -642,19 +642,7 @@ describe('TracerProxy', () => { tags: { '_dd.svc_src': 'm', }, - }, sinon.match.func) - assert.notStrictEqual(noop.trace.firstCall.args[2], callback) - }) - - it('should set service source tag when the span inside callback does a service override', () => { - const spanStub = { setTag: sinon.stub() } - const setTagSpy = spanStub.setTag - - noop.trace.callsFake((name, options, callback) => callback(spanStub)) - proxy.trace('a', span => span.setTag('service', 'b')) - assert.strictEqual(setTagSpy.callCount, 2) - sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }, callback) }) it('should ignore calls without an invalid callback', () => { @@ -694,7 +682,7 @@ describe('TracerProxy', () => { const returnValue = proxy.startSpan('a', 'b', 'c') sinon.assert.calledWith(noop.startSpan, 'a', 'b', 'c') - assert.strictEqual(returnValue._span, 'span') + assert.deepEqual(returnValue._span, { id: 'span' }) }) it('should set service source override tag when returned span does a setTag', () => { @@ -749,7 +737,7 @@ describe('TracerProxy', () => { }) describe('scope', () => { - it('should wrap the underlying noop scope', () => { + it('should return the underlying noop scope', () => { const scope = { active: sinon.stub().returns(null), activate: sinon.stub(), @@ -760,80 +748,8 @@ describe('TracerProxy', () => { const returnValue = proxy.scope() - assert.notStrictEqual(returnValue, scope) + assert.strictEqual(returnValue, scope) sinon.assert.calledOnce(noop.scope) - assert.strictEqual(returnValue._scope, scope) - }) - - it('should expose wrapped active spans and apply public span behavior', () => { - const span = { - context: sinon.stub().returns('context'), - setTag: sinon.spy(), - } - const scope = { - active: sinon.stub().returns(span), - activate: sinon.stub(), - bind: sinon.stub(), - } - - noop.scope.returns(scope) - - const activeSpan = proxy.scope().active() - - assert.notStrictEqual(activeSpan, span) - assert.strictEqual(activeSpan._span, span) - assert.strictEqual(activeSpan.context(), 'context') - - activeSpan.setTag('service', 'test-service') - - assert.strictEqual(span.setTag.callCount, 2) - sinon.assert.calledWith(span.setTag.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(span.setTag.secondCall, 'service', 'test-service') - sinon.assert.calledOnce(scope.active) - }) - - it('should unwrap public spans when activating a scope', () => { - const scope = { - active: sinon.stub().returns(null), - activate: sinon.stub().callsFake((span, fn) => fn()), - bind: sinon.stub(), - } - const internalSpan = { - context: sinon.stub(), - } - - noop.scope.returns(scope) - noop.startSpan.returns(internalSpan) - - const span = proxy.startSpan('test') - - const returnValue = proxy.scope().activate(span, () => 'result') - - assert.strictEqual(returnValue, 'result') - sinon.assert.calledOnce(scope.activate) - sinon.assert.calledWith(scope.activate, internalSpan, sinon.match.func) - }) - - it('should unwrap public spans when binding functions', () => { - const scope = { - active: sinon.stub().returns(null), - activate: sinon.stub(), - bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), - } - const internalSpan = { - context: sinon.stub(), - } - const fn = sinon.stub() - - noop.scope.returns(scope) - noop.startSpan.returns(internalSpan) - - const span = proxy.startSpan('test') - const returnValue = proxy.scope().bind(fn, span) - - assert.deepStrictEqual(returnValue, { fn, span: internalSpan }) - sinon.assert.calledOnce(scope.bind) - sinon.assert.calledWith(scope.bind, fn, internalSpan) }) }) @@ -982,7 +898,7 @@ describe('TracerProxy', () => { const callback = () => 'test' const returnValue = proxy.trace('a', 'b', callback) - sinon.assert.calledWith(tracer.trace, 'a', 'b', sinon.match.func) + sinon.assert.calledWith(tracer.trace, 'a', 'b', callback) assert.strictEqual(returnValue, 'test') }) @@ -995,26 +911,14 @@ describe('TracerProxy', () => { tags: { '_dd.svc_src': 'm', }, - }, sinon.match.func) - assert.notStrictEqual(tracer.trace.firstCall.args[2], callback) - }) - - it('should set service source tag when the span inside callback does a service override', () => { - const spanStub = { setTag: sinon.stub() } - const setTagSpy = spanStub.setTag - - tracer.trace.callsFake((name, options, callback) => callback(spanStub)) - proxy.trace('a', span => span.setTag('service', 'b')) - assert.strictEqual(setTagSpy.callCount, 2) - sinon.assert.calledWith(setTagSpy.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(setTagSpy.secondCall, 'service', 'b') + }, callback) }) it('should work without options', () => { const callback = () => 'test' const returnValue = proxy.trace('a', callback) - sinon.assert.calledWith(tracer.trace, 'a', {}, sinon.match.func) + sinon.assert.calledWith(tracer.trace, 'a', {}, callback) assert.strictEqual(returnValue, 'test') }) }) @@ -1057,7 +961,7 @@ describe('TracerProxy', () => { const returnValue = proxy.startSpan('a', 'b', 'c') sinon.assert.calledWith(tracer.startSpan, 'a', 'b', 'c') - assert.strictEqual(returnValue._span, 'span') + assert.deepEqual(returnValue._span, { id: 'span' }) }) it('should set service source override tag when returned span does a setTag', () => { @@ -1112,7 +1016,7 @@ describe('TracerProxy', () => { }) describe('scope', () => { - it('should wrap the underlying tracer scope', () => { + it('should return the underlying tracer scope', () => { const scope = { active: sinon.stub().returns(null), activate: sinon.stub(), @@ -1123,79 +1027,8 @@ describe('TracerProxy', () => { const returnValue = proxy.scope() - assert.notStrictEqual(returnValue, scope) + assert.strictEqual(returnValue, scope) sinon.assert.calledOnce(tracer.scope) - assert.strictEqual(returnValue._scope, scope) - }) - - it('should return wrapped active spans and apply public span behavior', () => { - const span = { - context: sinon.stub().returns('context'), - setTag: sinon.spy(), - } - const scope = { - active: sinon.stub().returns(span), - activate: sinon.stub(), - bind: sinon.stub(), - } - - tracer.scope.returns(scope) - - const activeSpan = proxy.scope().active() - - assert.notStrictEqual(activeSpan, span) - assert.strictEqual(activeSpan._span, span) - assert.strictEqual(activeSpan.context(), 'context') - - activeSpan.setTag('service', 'test-service') - - assert.strictEqual(span.setTag.callCount, 2) - sinon.assert.calledWith(span.setTag.firstCall, '_dd.svc_src', 'm') - sinon.assert.calledWith(span.setTag.secondCall, 'service', 'test-service') - sinon.assert.calledOnce(scope.active) - }) - - it('should unwrap public spans for activate', () => { - const scope = { - active: sinon.stub().returns(null), - activate: sinon.stub().callsFake((span, fn) => fn()), - bind: sinon.stub(), - } - const internalSpan = { - context: sinon.stub(), - } - - tracer.scope.returns(scope) - tracer.startSpan.returns(internalSpan) - - const span = proxy.startSpan('test') - const returnValue = proxy.scope().activate(span, () => 'result') - - assert.strictEqual(returnValue, 'result') - sinon.assert.calledOnce(scope.activate) - sinon.assert.calledWith(scope.activate, internalSpan, sinon.match.func) - }) - - it('should unwrap public spans for bind', () => { - const scope = { - active: sinon.stub().returns(null), - activate: sinon.stub(), - bind: sinon.stub().callsFake((fn, span) => ({ fn, span })), - } - const internalSpan = { - context: sinon.stub(), - } - const fn = sinon.stub() - - tracer.scope.returns(scope) - tracer.startSpan.returns(internalSpan) - - const span = proxy.startSpan('test') - const returnValue = proxy.scope().bind(fn, span) - - assert.deepStrictEqual(returnValue, { fn, span: internalSpan }) - sinon.assert.calledOnce(scope.bind) - sinon.assert.calledWith(scope.bind, fn, internalSpan) }) }) diff --git a/packages/dd-trace/test/scope.spec.js b/packages/dd-trace/test/scope.spec.js index d84e3113a47..de04d3d38a2 100644 --- a/packages/dd-trace/test/scope.spec.js +++ b/packages/dd-trace/test/scope.spec.js @@ -22,6 +22,14 @@ describe('Scope', () => { it('should return null by default', () => { assert.strictEqual(scope.active(), null) }) + + it('should return a PublicSpan wrapping the active span', () => { + scope.activate(span, () => { + const active = scope.active() + assert.notStrictEqual(active, span) + assert.strictEqual(active._span, span) + }) + }) }) describe('activate()', () => { @@ -45,16 +53,25 @@ describe('Scope', () => { assert.strictEqual(scope.active(), null) scope.activate(span, () => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()._span, span) }) assert.strictEqual(scope.active(), null) }) + it('should unwrap a PublicSpan before activating', () => { + scope.activate(span, () => { + const publicSpan = scope.active() + scope.activate(publicSpan, () => { + assert.strictEqual(scope.active()._span, span) + }) + }) + }) + it('should persist through setTimeout', done => { scope.activate(span, () => { setTimeout(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) done() }, 0) }) @@ -63,7 +80,7 @@ describe('Scope', () => { it('should persist through setImmediate', done => { scope.activate(span, () => { setImmediate(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) done() }, 0) }) @@ -74,7 +91,7 @@ describe('Scope', () => { let shouldReturn = false const timer = setInterval(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) if (shouldReturn) { clearInterval(timer) @@ -89,7 +106,7 @@ describe('Scope', () => { it('should persist through process.nextTick', done => { scope.activate(span, () => { process.nextTick(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) done() }, 0) }) @@ -100,7 +117,7 @@ describe('Scope', () => { return scope.activate(span, () => { return promise.then(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) }) }) }) @@ -108,7 +125,7 @@ describe('Scope', () => { it('should handle concurrency', done => { scope.activate(span, () => { setImmediate(() => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) done() }) }) @@ -135,7 +152,7 @@ describe('Scope', () => { describe('with a function', () => { it('should bind the function to the active span', () => { let fn = () => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) } scope.activate(span, () => { @@ -147,7 +164,7 @@ describe('Scope', () => { it('should bind the function to the provided span', () => { let fn = () => { - assert.strictEqual(scope.active(), span) + assert.strictEqual(scope.active()?._span, span) } fn = scope.bind(fn, span) diff --git a/packages/dd-trace/test/tracer.spec.js b/packages/dd-trace/test/tracer.spec.js index ceea9cabeed..68707deabc3 100644 --- a/packages/dd-trace/test/tracer.spec.js +++ b/packages/dd-trace/test/tracer.spec.js @@ -52,7 +52,7 @@ describe('Tracer', () => { describe('trace', () => { it('should run the callback with a new span', () => { tracer.trace('name', {}, span => { - assert.ok(span instanceof Span) + assert.ok(span._span instanceof Span) assert.strictEqual(span.context()._name, 'name') }) }) @@ -68,7 +68,7 @@ describe('Tracer', () => { } tracer.trace('name', options, span => { - assert.ok(span instanceof Span) + assert.ok(span._span instanceof Span) assertObjectContains(span.context()._tags, options.tags) assertObjectContains(span.context()._tags, { [SERVICE_NAME]: 'service', @@ -138,7 +138,7 @@ describe('Tracer', () => { let span tracer.trace('name', {}, (_span) => { - span = _span + span = _span._span sinon.spy(span, 'finish') }) @@ -151,8 +151,8 @@ describe('Tracer', () => { try { tracer.trace('name', {}, _span => { - span = _span - tags = span.context()._tags + span = _span._span + tags = _span.context()._tags sinon.spy(span, 'finish') throw new Error('boom') }) @@ -172,7 +172,7 @@ describe('Tracer', () => { let done tracer.trace('name', {}, (_span, _done) => { - span = _span + span = _span._span sinon.spy(span, 'finish') done = _done }) @@ -191,8 +191,8 @@ describe('Tracer', () => { let done tracer.trace('name', {}, (_span, _done) => { - span = _span - tags = span.context()._tags + span = _span._span + tags = _span.context()._tags sinon.spy(span, 'finish') done = _done }) @@ -219,7 +219,7 @@ describe('Tracer', () => { tracer .trace('name', {}, _span => { - span = _span + span = _span._span sinon.spy(span, 'finish') return promise }) @@ -240,8 +240,8 @@ describe('Tracer', () => { tracer .trace('name', {}, _span => { - span = _span - tags = span.context()._tags + span = _span._span + tags = _span.context()._tags sinon.spy(span, 'finish') return Promise.reject(new Error('boom')) }) @@ -326,7 +326,7 @@ describe('Tracer', () => { it('should wait for the callback to be called before finishing the span', done => { const fn = tracer.wrap('name', {}, sinon.spy(function (cb) { - const span = tracer.scope().active() + const span = tracer.scope().active()._span sinon.spy(span, 'finish') From ccd34eccba21567eb69d6cc8f69b6772f7a15ce5 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 15:38:43 -0400 Subject: [PATCH 15/48] wrap tracer --- packages/dd-trace/src/tracer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index 4b73445cd99..87ed015cbee 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -61,7 +61,7 @@ class DatadogTracer extends Tracer { trace (name, options, fn) { options = { childOf: this.scope().active(), ...options } - const span = PublicSpan.wrap(this.startSpan(name, options)) + const span = new PublicSpan(this.startSpan(name, options)) addTags(span, options) From 95fff2a5ed60b1b9b37f808275187a83fe235a15 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 16:48:10 -0400 Subject: [PATCH 16/48] set back wrap and fixed tests --- .../test/index.spec.js | 2 +- .../test/aws-sdk.spec.js | 2 +- packages/datadog-plugin-fs/test/index.spec.js | 4 ++-- .../datadog-plugin-grpc/test/client.spec.js | 4 ++-- .../datadog-plugin-http2/test/client.spec.js | 2 +- .../datadog-plugin-ioredis/test/index.spec.js | 2 +- .../test/index.spec.js | 2 +- .../test/index.spec.js | 12 +++++----- .../test/index.spec.js | 4 ++-- .../datadog-plugin-redis/test/client.spec.js | 4 ++-- packages/dd-trace/src/noop/proxy.js | 2 +- .../dd-trace/src/opentracing/public/scope.js | 24 +++++++++++++++++++ .../dd-trace/src/opentracing/public/span.js | 22 +++++++++++------ packages/dd-trace/src/scope.js | 6 +++-- packages/dd-trace/src/tracer.js | 2 +- 15 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 packages/dd-trace/src/opentracing/public/scope.js diff --git a/packages/datadog-plugin-aerospike/test/index.spec.js b/packages/datadog-plugin-aerospike/test/index.spec.js index f27d939ad85..7039f6e62a4 100644 --- a/packages/datadog-plugin-aerospike/test/index.spec.js +++ b/packages/datadog-plugin-aerospike/test/index.spec.js @@ -247,7 +247,7 @@ describe('Plugin', () => { aerospike.connect(config).then(client => { tracer.scope().activate(span, () => { client.put(key, { i: 123 }, () => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) client.close(false) done() }) diff --git a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js index d1cafe322cd..b9bf5ecb86e 100644 --- a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js @@ -206,7 +206,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { s3.listBuckets({}, () => { try { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() } catch (e) { done(e) diff --git a/packages/datadog-plugin-fs/test/index.spec.js b/packages/datadog-plugin-fs/test/index.spec.js index b481acee592..5ce9f3f6561 100644 --- a/packages/datadog-plugin-fs/test/index.spec.js +++ b/packages/datadog-plugin-fs/test/index.spec.js @@ -1887,7 +1887,7 @@ describe('Plugin', () => { span.finish() return tracer.scope().activate(span, () => { args.push((err) => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) if (err) { if (withError) withError(err) else done(err) @@ -1908,7 +1908,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return fs.promises[name].apply(fs.promises, args) .then(() => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) .catch((err) => { if (withError) withError(err) diff --git a/packages/datadog-plugin-grpc/test/client.spec.js b/packages/datadog-plugin-grpc/test/client.spec.js index f88d6a7acb5..6c1eef00667 100644 --- a/packages/datadog-plugin-grpc/test/client.spec.js +++ b/packages/datadog-plugin-grpc/test/client.spec.js @@ -519,7 +519,7 @@ describe('Plugin', () => { }).then(client => { tracer.scope().activate(span, () => { client.getUnary({ first: 'foobar' }, (err, response) => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done(err) }) }) @@ -539,7 +539,7 @@ describe('Plugin', () => { const call = client.getServerStream({ first: 'foobar' }) call.on('data', () => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() }) }) diff --git a/packages/datadog-plugin-http2/test/client.spec.js b/packages/datadog-plugin-http2/test/client.spec.js index caf8e34d091..460aa6c6e6c 100644 --- a/packages/datadog-plugin-http2/test/client.spec.js +++ b/packages/datadog-plugin-http2/test/client.spec.js @@ -516,7 +516,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { const req = client.request({ ':path': '/user' }) req.on('response', (headers, flags) => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() }) diff --git a/packages/datadog-plugin-ioredis/test/index.spec.js b/packages/datadog-plugin-ioredis/test/index.spec.js index 21633615d8a..327fc5513c8 100644 --- a/packages/datadog-plugin-ioredis/test/index.spec.js +++ b/packages/datadog-plugin-ioredis/test/index.spec.js @@ -60,7 +60,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, async () => { await redis.get('foo') - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) diff --git a/packages/datadog-plugin-iovalkey/test/index.spec.js b/packages/datadog-plugin-iovalkey/test/index.spec.js index 70dfc877cd8..9a1487cc563 100644 --- a/packages/datadog-plugin-iovalkey/test/index.spec.js +++ b/packages/datadog-plugin-iovalkey/test/index.spec.js @@ -61,7 +61,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, async () => { await valkey.get('foo') - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) diff --git a/packages/datadog-plugin-mongoose/test/index.spec.js b/packages/datadog-plugin-mongoose/test/index.spec.js index 9ffd9bf96fe..c2a35cdb268 100644 --- a/packages/datadog-plugin-mongoose/test/index.spec.js +++ b/packages/datadog-plugin-mongoose/test/index.spec.js @@ -80,7 +80,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return kitty.save().then(() => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) }) @@ -94,7 +94,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { Cat.find({ name: 'Zildjian' }).exec(() => { try { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() } catch (e) { done(e) @@ -114,7 +114,7 @@ describe('Plugin', () => { ) Cat.aggregate([{ $match: { name: 'Zildjian' } }]).exec(() => { try { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) done() } catch (e) { done(e) @@ -134,7 +134,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return promise.then(() => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) }) @@ -146,7 +146,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return Cat.find({ name: 'Zildjian' }).exec().then(() => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) }) @@ -158,7 +158,7 @@ describe('Plugin', () => { return tracer.scope().activate(span, () => { return Cat.aggregate([{ $match: { name: 'Zildjian' } }]).exec().then(() => { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) }) diff --git a/packages/datadog-plugin-oracledb/test/index.spec.js b/packages/datadog-plugin-oracledb/test/index.spec.js index a3724a9612c..8f4bac6ca87 100644 --- a/packages/datadog-plugin-oracledb/test/index.spec.js +++ b/packages/datadog-plugin-oracledb/test/index.spec.js @@ -108,7 +108,7 @@ describe('Plugin', () => { const span = tracer.startSpan('test') return tracer.scope().activate(span, async () => { await connection.execute(dbQuery) - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) @@ -135,7 +135,7 @@ describe('Plugin', () => { tracer.scope().activate(span, () => { connection.execute(dbQuery, () => { try { - assert.strictEqual(tracer.scope().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) } catch (e) { return done(e) } diff --git a/packages/datadog-plugin-redis/test/client.spec.js b/packages/datadog-plugin-redis/test/client.spec.js index 774b2dd9c25..64562d8bbae 100644 --- a/packages/datadog-plugin-redis/test/client.spec.js +++ b/packages/datadog-plugin-redis/test/client.spec.js @@ -136,10 +136,10 @@ describe('Plugin', () => { ) it('should restore the parent context in the callback', async () => { - const span = {} + const span = tracer.startSpan('test') tracer.scope().activate(span, () => { client.get('foo', () => { - assert.strictEqual(span.context().active()._span, span) + assert.strictEqual(tracer.scope().active(), span) }) }) }) diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 54f705df761..7b6d0c50a90 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -93,7 +93,7 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) + return PublicSpan.wrap(this._tracer.startSpan.apply(this._tracer, arguments)) } inject () { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js new file mode 100644 index 00000000000..5321fbb5788 --- /dev/null +++ b/packages/dd-trace/src/opentracing/public/scope.js @@ -0,0 +1,24 @@ +'use strict' + +const PublicSpan = require('./span') + +class Scope { + constructor (scope) { + this._scope = scope + } + + active () { + const span = this._scope.active() + return span ? PublicSpan.wrap(span) : null + } + + activate (span, fn) { + return this._scope.activate(span?._span || span, fn) + } + + bind (fn, span) { + return this._scope.bind(fn, span?._span || span) + } +} + +module.exports = Scope diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 574fbe892a8..6f9abfcad80 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -1,5 +1,10 @@ 'use strict' +// A WeakMap cache at module scope ensures the same wrapper instance is returned +// for the same underlying span across all subclasses, so reference equality +// checks (===) in user code remain stable. +const cache = new WeakMap() + const { SVC_SRC_KEY } = require('../../constants') const SERVICE_KEY = 'service' @@ -10,20 +15,23 @@ const SERVICE_NAME_KEY = 'service.name' * external usage and acting accordingly. */ class PublicSpan { - // A WeakMap cache ensures the same wrapper instance is returned for the same - // underlying span, so reference equality checks (===) in user code remain stable. - static #cache = new WeakMap() - constructor (span) { + this._span = span + } + + static wrap (span) { if (span instanceof PublicSpan) { return span } - const cached = PublicSpan.#cache.get(span) + const cached = cache.get(span) if (cached !== undefined) { return cached } - this._span = span - PublicSpan.#cache.set(span, this) + const wrapper = new PublicSpan(span) + try { + cache.set(span, wrapper) + } catch {} + return wrapper } setTag (key, value) { diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index a53e629fefb..8c77644147b 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -12,7 +12,7 @@ class Scope { const store = storage('legacy').getStore() const span = (store && store.span) || null - return span ? new PublicSpan(span) : null + return span ? PublicSpan.wrap(span) : null } activate (span, callback) { @@ -41,8 +41,10 @@ class Scope { bind (fn, span) { if (typeof fn !== 'function') return fn + span = span?._span || span + const scope = this - const spanOrActive = this._spanOrActive(span?._span || span) + const spanOrActive = this._spanOrActive(span) const bound = function () { return scope.activate(spanOrActive, () => { diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index 87ed015cbee..4b73445cd99 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -61,7 +61,7 @@ class DatadogTracer extends Tracer { trace (name, options, fn) { options = { childOf: this.scope().active(), ...options } - const span = new PublicSpan(this.startSpan(name, options)) + const span = PublicSpan.wrap(this.startSpan(name, options)) addTags(span, options) From b088cb8008fcbb41287d7e59424423f35c657fe8 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 17:04:43 -0400 Subject: [PATCH 17/48] fix llmobs sdk --- packages/dd-trace/src/llmobs/sdk.js | 8 +++++--- packages/dd-trace/test/llmobs/sdk/index.spec.js | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index 6e06027953c..b9ddfba08ce 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -7,6 +7,7 @@ const tracerVersion = require('../../../../package.json').version const logger = require('../log') const { getValueFromEnvSources } = require('../config/helper') const Span = require('../opentracing/span') +const PublicSpan = require('../opentracing/public/span') const { SPAN_KIND, OUTPUT_VALUE, INPUT_VALUE } = require('./constants/tags') const { getFunctionArguments, @@ -209,7 +210,7 @@ class LLMObs extends NoopLLMObs { span = this._active() } - if ((span && !options) && !(span instanceof Span)) { + if ((span && !options) && !(span instanceof Span) && !(span instanceof PublicSpan)) { options = span span = this._active() } @@ -230,7 +231,8 @@ class LLMObs extends NoopLLMObs { err = 'invalid_span_type' throw new Error('Span must be an LLMObs-generated span') } - if (span._duration !== undefined) { + const rawSpan = span instanceof PublicSpan ? span._span : span + if (rawSpan._duration !== undefined) { err = 'invalid_finished_span' throw new Error('Cannot annotate a finished span') } @@ -287,7 +289,7 @@ class LLMObs extends NoopLLMObs { err = 'no_active_span' throw new Error('No span provided and no active LLMObs-generated span found') } - if (!(span instanceof Span)) { + if (!(span instanceof Span) && !(span instanceof PublicSpan)) { err = 'invalid_span' throw new TypeError('Span must be a valid Span object.') } diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index 1a9f72e31fa..9ba1edb69a3 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -708,7 +708,7 @@ describe('sdk', () => { function outerLLMObs () { outerLLMObsSpan = llmobs._active() - assert.strictEqual(outerLLMObsSpan, tracer.scope().active()._span) + assert.strictEqual(outerLLMObsSpan, tracer.scope().active()) apmWrapped() } @@ -718,7 +718,7 @@ describe('sdk', () => { } function innerLLMObs () { innerLLMObsSpan = llmobs._active() - assert.strictEqual(innerLLMObsSpan, tracer.scope().active()._span) + assert.strictEqual(innerLLMObsSpan, tracer.scope().active()) assert.strictEqual( LLMObsTagger.tagMap.get(innerLLMObsSpan)['_ml_obs.llmobs_parent_id'], outerLLMObsSpan.context().toSpanId() @@ -766,12 +766,12 @@ describe('sdk', () => { function outer () { outerSpan = llmobs._active() wrappedInner1(() => {}) - assert.strictEqual(outerSpan, tracer.scope().active()._span) + assert.strictEqual(outerSpan, tracer.scope().active()) wrappedInner2() } function inner1 (cb) { - const inner = tracer.scope().active()._span + const inner = tracer.scope().active() assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], @@ -781,7 +781,7 @@ describe('sdk', () => { } function inner2 () { - const inner = tracer.scope().active()._span + const inner = tracer.scope().active() assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], From fe92453d15d19dc9b4587c8546f0a734d9c29736 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 17:34:53 -0400 Subject: [PATCH 18/48] fix llmobs and appsec --- .../dd-trace/src/llmobs/span_processor.js | 33 +++++++++++++++---- packages/dd-trace/src/priority_sampler.js | 4 ++- .../dd-trace/test/appsec/sdk/utils.spec.js | 3 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 0e873b6a5d9..8c119e4ae11 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -9,6 +9,7 @@ const { ERROR_TYPE, ERROR_STACK, } = require('../constants') +const PublicSpan = require('../opentracing/public/span') const { SPAN_KIND, MODEL_NAME, @@ -35,6 +36,19 @@ const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') +/** + * Resolves the tagMap key for a raw span from the finish channel. + * Plugin-instrumented spans are registered with raw Span instances, + * while SDK spans are registered with PublicSpan wrappers. + * @param {import('../opentracing/span')} span - raw span from the finish channel + * @returns {import('../opentracing/span') | PublicSpan | undefined} + */ +function resolveTagMapKey (span) { + if (LLMObsTagger.tagMap.has(span)) return span + const wrapped = PublicSpan.wrap(span) + if (LLMObsTagger.tagMap.has(wrapped)) return wrapped +} + class LLMObservabilitySpan { constructor () { this.input = [] @@ -73,15 +87,19 @@ class LLMObsSpanProcessor { // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation? process (span) { if (!this.#config.llmobs.enabled) return + // The finish channel always publishes raw Span instances, but the tagMap + // may be keyed by either a raw Span (plugin-instrumented) or a PublicSpan + // wrapper (SDK-instrumented). Resolve to whichever key is present. + const tagMapKey = resolveTagMapKey(span) // if the span is not in our private tagger map, it is not an llmobs span - if (!LLMObsTagger.tagMap.has(span)) return + if (!tagMapKey) return try { - const formattedEvent = this.format(span) - telemetry.incrementLLMObsSpanFinishedCount(span) + const formattedEvent = this.format(span, tagMapKey) + telemetry.incrementLLMObsSpanFinishedCount(tagMapKey) if (formattedEvent == null) return - const mlObsTags = LLMObsTagger.tagMap.get(span) + const mlObsTags = LLMObsTagger.tagMap.get(tagMapKey) const routing = { apiKey: mlObsTags[ROUTING_API_KEY], site: mlObsTags[ROUTING_SITE], @@ -99,12 +117,13 @@ class LLMObsSpanProcessor { } } - format (span) { + format (span, tagMapKey) { + if (!tagMapKey) tagMapKey = resolveTagMapKey(span) const llmObsSpan = new LLMObservabilitySpan() let inputType, outputType const spanTags = span.context()._tags - const mlObsTags = LLMObsTagger.tagMap.get(span) + const mlObsTags = LLMObsTagger.tagMap.get(tagMapKey) const spanKind = mlObsTags[SPAN_KIND] @@ -156,7 +175,7 @@ class LLMObsSpanProcessor { const name = mlObsTags[NAME] || span._name - const tags = this.#getTags(span, mlApp, sessionId, error) + const tags = this.#getTags(tagMapKey, mlApp, sessionId, error) llmObsSpan._tags = tags const processedSpan = this.#runProcessor(llmObsSpan) diff --git a/packages/dd-trace/src/priority_sampler.js b/packages/dd-trace/src/priority_sampler.js index 6edfc39c81a..8b79796617f 100644 --- a/packages/dd-trace/src/priority_sampler.js +++ b/packages/dd-trace/src/priority_sampler.js @@ -388,7 +388,9 @@ class PrioritySampler { * @param {Product} [product] */ static keepTrace (span, product) { - span?._prioritySampler?.setPriority(span, USER_KEEP, product) + // Unwrap PublicSpan (scope().active()) so _prioritySampler is reachable. + const target = span?._span ?? span + target?._prioritySampler?.setPriority(target, USER_KEEP, product) } } diff --git a/packages/dd-trace/test/appsec/sdk/utils.spec.js b/packages/dd-trace/test/appsec/sdk/utils.spec.js index 84457943d17..752c0d9acdb 100644 --- a/packages/dd-trace/test/appsec/sdk/utils.spec.js +++ b/packages/dd-trace/test/appsec/sdk/utils.spec.js @@ -102,8 +102,7 @@ describe('Appsec SDK utils', () => { tracer.trace('child1.2', { childOf }, child12 => { tracer.trace('child1.2.1', { childOf: child12 }, child121 => { const root = getRootSpan(tracer) - - assert.strictEqual(root, child12) + assert.strictEqual(root, child12._span) }) }) }) From 6e52e67ee9e1d2c57a81d96cb5703de692465b6d Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 18:24:00 -0400 Subject: [PATCH 19/48] read methods from DatadogSpan --- .../dd-trace/src/opentracing/public/span.js | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 6f9abfcad80..7eb126934a4 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -6,6 +6,7 @@ const cache = new WeakMap() const { SVC_SRC_KEY } = require('../../constants') +const DatadogSpan = require('../span') const SERVICE_KEY = 'service' const SERVICE_NAME_KEY = 'service.name' @@ -52,25 +53,11 @@ class PublicSpan { } // Whenever a method needs to be modified to have a unique public behavior, it -// should be removed from this list. -for (const method of [ - 'context', - 'tracer', - 'setOperationName', - 'setBaggageItem', - 'getBaggageItem', - 'getAllBaggageItems', - 'removeBaggageItem', - 'removeAllBaggageItems', - 'log', - 'logEvent', - 'addLink', - 'addLinks', - 'addSpanPointer', - 'addEvent', - 'finish', - 'toString', -]) { +// should be implemented on `PublicSpan` directly so it is skipped here. +for (const method of Object.getOwnPropertyNames(DatadogSpan.prototype)) { + if (method === 'constructor' || method.startsWith('_') || PublicSpan.prototype[method]) { + continue + } PublicSpan.prototype[method] = function (...args) { const result = this._span[method](...args) // always return wrapper span when the result is the span itself From 5eba124593c4929321798554a8906d2ac3fad983 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 18:38:27 -0400 Subject: [PATCH 20/48] retore dd-trace-api tests --- .../test/index.spec.js | 97 +++++++------------ 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/packages/datadog-plugin-dd-trace-api/test/index.spec.js b/packages/datadog-plugin-dd-trace-api/test/index.spec.js index e85a163f630..5030133da78 100644 --- a/packages/datadog-plugin-dd-trace-api/test/index.spec.js +++ b/packages/datadog-plugin-dd-trace-api/test/index.spec.js @@ -92,48 +92,40 @@ describe('Plugin', () => { describe('scope:active', () => { it('should call underlying api', () => { scope = tracer.scope() - const internalScope = scope._scope || scope - sinon.spy(internalScope, 'active') + sinon.spy(scope, 'active') testChannel({ name: 'scope:active', - fn: internalScope.active, + fn: scope.active, self: dummyScope, ret: null, - thisValue: internalScope, }) - internalScope.active.restore() + scope.active.restore() }) }) describe('scope:activate', () => { it('should call underlying api', () => { scope = tracer.scope() - const internalScope = scope._scope || scope - sinon.spy(internalScope, 'activate') + sinon.spy(scope, 'activate') testChannel({ name: 'scope:activate', - fn: internalScope.activate, + fn: scope.activate, self: dummyScope, - args: [undefined, undefined], - thisValue: internalScope, }) - internalScope.activate.restore() + scope.activate.restore() }) }) describe('scope:bind', () => { it('should call underlying api', () => { scope = tracer.scope() - const internalScope = scope._scope || scope - sinon.spy(internalScope, 'bind') + sinon.spy(scope, 'bind') testChannel({ name: 'scope:bind', - fn: internalScope.bind, + fn: scope.bind, self: dummyScope, - args: [undefined, undefined], - thisValue: internalScope, }) - internalScope.bind.restore() + scope.bind.restore() }) }) }) @@ -143,8 +135,6 @@ describe('Plugin', () => { let dummySpanContext let span let spanContext - let internalSpan - let internalSpanContext it('should call underlying api', () => { dummySpan = {} @@ -154,7 +144,7 @@ describe('Plugin', () => { ret: dummySpan, }) span = tracer.startSpan.getCall(0).returnValue - internalSpan = span._span || span + span = span._span || span }) describe('span:context', () => { @@ -164,30 +154,27 @@ describe('Plugin', () => { it('should call underlying api', () => { dummySpanContext = {} - sinon.spy(internalSpan, 'context') + sinon.spy(span, 'context') testChannel({ name: 'span:context', - fn: internalSpan.context, + fn: span.context, self: dummySpan, ret: dummySpanContext, - thisValue: internalSpan, }) - spanContext = internalSpan.context.getCall(0).returnValue - internalSpanContext = spanContext._spanContext || spanContext - sinon.stub(internalSpanContext, 'toTraceId').callsFake(() => traceId) - sinon.stub(internalSpanContext, 'toSpanId').callsFake(() => spanId) - sinon.stub(internalSpanContext, 'toTraceparent').callsFake(() => traceparent) - internalSpan.context.restore() + spanContext = span.context.getCall(0).returnValue + sinon.stub(spanContext, 'toTraceId').callsFake(() => traceId) + sinon.stub(spanContext, 'toSpanId').callsFake(() => spanId) + sinon.stub(spanContext, 'toTraceparent').callsFake(() => traceparent) + span.context.restore() }) describe('context:toTraceId', () => { it('should call underlying api', () => { testChannel({ name: 'context:toTraceId', - fn: internalSpanContext.toTraceId, + fn: spanContext.toTraceId, self: dummySpanContext, ret: traceId, - thisValue: internalSpanContext, }) }) }) @@ -196,10 +183,9 @@ describe('Plugin', () => { it('should call underlying api', () => { testChannel({ name: 'context:toSpanId', - fn: internalSpanContext.toSpanId, + fn: spanContext.toSpanId, self: dummySpanContext, ret: spanId, - thisValue: internalSpanContext, }) }) }) @@ -208,10 +194,9 @@ describe('Plugin', () => { it('should call underlying api', () => { testChannel({ name: 'context:toTraceparent', - fn: internalSpanContext.toTraceparent, + fn: spanContext.toTraceparent, self: dummySpanContext, ret: traceparent, - thisValue: internalSpanContext, }) }) }) @@ -219,60 +204,56 @@ describe('Plugin', () => { describe('span:setTag', () => { it('should call underlying api', () => { - sinon.spy(internalSpan, 'setTag') + sinon.spy(span, 'setTag') testChannel({ name: 'span:setTag', - fn: internalSpan.setTag, + fn: span.setTag, self: dummySpan, ret: dummySpan, - args: ['test.tag', 'test.value'], - thisValue: internalSpan, + args: ['key', 'value'], }) - internalSpan.setTag.restore() + span.setTag.restore() }) }) describe('span:addTags', () => { it('should call underlying api', () => { - sinon.spy(internalSpan, 'addTags') + sinon.spy(span, 'addTags') testChannel({ name: 'span:addTags', - fn: internalSpan.addTags, + fn: span.addTags, self: dummySpan, ret: dummySpan, - args: [{ 'test.tag': 'test.value' }], - thisValue: internalSpan, + args: [{ key: 'value' }], }) - internalSpan.addTags.restore() + span.addTags.restore() }) }) describe('span:finish', () => { it('should call underlying api', () => { - sinon.spy(internalSpan, 'finish') + sinon.spy(span, 'finish') testChannel({ name: 'span:finish', - fn: internalSpan.finish, + fn: span.finish, self: dummySpan, - args: [undefined], - thisValue: internalSpan, + args: [123], }) - internalSpan.finish.restore() + span.finish.restore() }) }) describe('span:addLink', () => { it('should call underlying api', () => { - sinon.spy(internalSpan, 'addLink') + sinon.spy(span, 'addLink') testChannel({ name: 'span:addLink', - fn: internalSpan.addLink, + fn: span.addLink, self: dummySpan, ret: dummySpan, args: [dummySpanContext], - thisValue: internalSpan, }) - internalSpan.addLink.restore() + span.addLink.restore() }) }) }) @@ -329,7 +310,7 @@ describe('Plugin', () => { }) } - function testChannel ({ name, fn, self = dummyTracer, ret, args = [], proxy, thisValue }) { + function testChannel ({ name, fn, self = dummyTracer, ret, args = [], proxy }) { testedChannels.add('datadog-api:v1:' + name) const ch = dc.channel('datadog-api:v1:' + name) if (proxy === undefined) { @@ -341,11 +322,7 @@ describe('Plugin', () => { throw payload.ret.error } assert.strictEqual(payload.ret.value, ret) - sinon.assert.calledOnce(fn) - assert.deepStrictEqual(fn.args, [args]) - if (typeof thisValue !== 'undefined') { - assert.strictEqual(fn.thisValues[0], thisValue) - } + sinon.assert.calledOnceWithExactly(fn, ...args) } }) }) From 8267e3aa1fd533f57ff900a928bfabf73771682a Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 18:43:23 -0400 Subject: [PATCH 21/48] removed spies --- .../datadog-plugin-dd-trace-api/test/index.spec.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/datadog-plugin-dd-trace-api/test/index.spec.js b/packages/datadog-plugin-dd-trace-api/test/index.spec.js index 5030133da78..3f030b5b815 100644 --- a/packages/datadog-plugin-dd-trace-api/test/index.spec.js +++ b/packages/datadog-plugin-dd-trace-api/test/index.spec.js @@ -145,6 +145,7 @@ describe('Plugin', () => { }) span = tracer.startSpan.getCall(0).returnValue span = span._span || span + sinon.spy(span) }) describe('span:context', () => { @@ -154,7 +155,6 @@ describe('Plugin', () => { it('should call underlying api', () => { dummySpanContext = {} - sinon.spy(span, 'context') testChannel({ name: 'span:context', fn: span.context, @@ -165,7 +165,6 @@ describe('Plugin', () => { sinon.stub(spanContext, 'toTraceId').callsFake(() => traceId) sinon.stub(spanContext, 'toSpanId').callsFake(() => spanId) sinon.stub(spanContext, 'toTraceparent').callsFake(() => traceparent) - span.context.restore() }) describe('context:toTraceId', () => { @@ -204,7 +203,6 @@ describe('Plugin', () => { describe('span:setTag', () => { it('should call underlying api', () => { - sinon.spy(span, 'setTag') testChannel({ name: 'span:setTag', fn: span.setTag, @@ -212,13 +210,11 @@ describe('Plugin', () => { ret: dummySpan, args: ['key', 'value'], }) - span.setTag.restore() }) }) describe('span:addTags', () => { it('should call underlying api', () => { - sinon.spy(span, 'addTags') testChannel({ name: 'span:addTags', fn: span.addTags, @@ -226,26 +222,22 @@ describe('Plugin', () => { ret: dummySpan, args: [{ key: 'value' }], }) - span.addTags.restore() }) }) describe('span:finish', () => { it('should call underlying api', () => { - sinon.spy(span, 'finish') testChannel({ name: 'span:finish', fn: span.finish, self: dummySpan, args: [123], }) - span.finish.restore() }) }) describe('span:addLink', () => { it('should call underlying api', () => { - sinon.spy(span, 'addLink') testChannel({ name: 'span:addLink', fn: span.addLink, @@ -253,7 +245,6 @@ describe('Plugin', () => { ret: dummySpan, args: [dummySpanContext], }) - span.addLink.restore() }) }) }) From c290b61eca94496f057398ed2f7cad13ea280fb6 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 19:15:39 -0400 Subject: [PATCH 22/48] moved wrap into constructor --- packages/datadog-plugin-ws/test/index.spec.js | 5 ++--- packages/dd-trace/src/llmobs/span_processor.js | 2 +- packages/dd-trace/src/noop/proxy.js | 2 +- packages/dd-trace/src/opentracing/public/scope.js | 2 +- packages/dd-trace/src/opentracing/public/span.js | 9 ++------- packages/dd-trace/src/scope.js | 2 +- packages/dd-trace/src/tracer.js | 2 +- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/datadog-plugin-ws/test/index.spec.js b/packages/datadog-plugin-ws/test/index.spec.js index 6df13ee8251..12cd2aa899e 100644 --- a/packages/datadog-plugin-ws/test/index.spec.js +++ b/packages/datadog-plugin-ws/test/index.spec.js @@ -643,8 +643,8 @@ describe('Plugin', () => { let didFindPointerLink = false await agent.assertSomeTraces(traces => { - const producerSpan = traces.flat().find(s => s.name === 'websocket.send') - assert.ok(producerSpan, 'Should have a producer span') + const producerSpan = traces[0][0] + assert.strictEqual(producerSpan.name, 'websocket.send') assert.strictEqual(producerSpan.service, 'ws-with-pointers') // Check for span links with span pointer attributes @@ -653,7 +653,6 @@ describe('Plugin', () => { const pointerLink = spanLinks.find(link => link.attributes && link.attributes['dd.kind'] === 'span-pointer' ) - assert.ok(pointerLink, 'Should have a span pointer link') assertObjectContains(pointerLink, { attributes: { diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 8c119e4ae11..21277f119d9 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -45,7 +45,7 @@ const LLMObsTagger = require('./tagger') */ function resolveTagMapKey (span) { if (LLMObsTagger.tagMap.has(span)) return span - const wrapped = PublicSpan.wrap(span) + const wrapped = new PublicSpan(span) if (LLMObsTagger.tagMap.has(wrapped)) return wrapped } diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 7b6d0c50a90..54f705df761 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -93,7 +93,7 @@ class NoopProxy { [SVC_SRC_KEY]: 'm', } } - return PublicSpan.wrap(this._tracer.startSpan.apply(this._tracer, arguments)) + return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) } inject () { diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js index 5321fbb5788..a9b6a123cd0 100644 --- a/packages/dd-trace/src/opentracing/public/scope.js +++ b/packages/dd-trace/src/opentracing/public/scope.js @@ -9,7 +9,7 @@ class Scope { active () { const span = this._scope.active() - return span ? PublicSpan.wrap(span) : null + return span ? new PublicSpan(span) : null } activate (span, fn) { diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 7eb126934a4..3f9ef98dc69 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -17,10 +17,6 @@ const SERVICE_NAME_KEY = 'service.name' */ class PublicSpan { constructor (span) { - this._span = span - } - - static wrap (span) { if (span instanceof PublicSpan) { return span } @@ -28,11 +24,10 @@ class PublicSpan { if (cached !== undefined) { return cached } - const wrapper = new PublicSpan(span) + this._span = span try { - cache.set(span, wrapper) + cache.set(span, this) } catch {} - return wrapper } setTag (key, value) { diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index 8c77644147b..d03961b0af1 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -12,7 +12,7 @@ class Scope { const store = storage('legacy').getStore() const span = (store && store.span) || null - return span ? PublicSpan.wrap(span) : null + return span ? new PublicSpan(span) : null } activate (span, callback) { diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index 4b73445cd99..87ed015cbee 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -61,7 +61,7 @@ class DatadogTracer extends Tracer { trace (name, options, fn) { options = { childOf: this.scope().active(), ...options } - const span = PublicSpan.wrap(this.startSpan(name, options)) + const span = new PublicSpan(this.startSpan(name, options)) addTags(span, options) From 8a6477af0f9b0092cf2628f4a8dfeb86ceb4e6ed Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 30 Mar 2026 21:52:51 -0400 Subject: [PATCH 23/48] simplified code and removed some changes --- packages/datadog-plugin-ws/test/index.spec.js | 2 +- packages/dd-trace/src/aiguard/sdk.js | 6 ++--- packages/dd-trace/src/llmobs/sdk.js | 26 ++++++++++++++++--- .../dd-trace/src/llmobs/span_processor.js | 11 +++----- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/datadog-plugin-ws/test/index.spec.js b/packages/datadog-plugin-ws/test/index.spec.js index 12cd2aa899e..563dcb4fcfc 100644 --- a/packages/datadog-plugin-ws/test/index.spec.js +++ b/packages/datadog-plugin-ws/test/index.spec.js @@ -653,6 +653,7 @@ describe('Plugin', () => { const pointerLink = spanLinks.find(link => link.attributes && link.attributes['dd.kind'] === 'span-pointer' ) + assert.ok(pointerLink, 'Should have a span pointer link') assertObjectContains(pointerLink, { attributes: { @@ -697,7 +698,6 @@ describe('Plugin', () => { const pointerLink = spanLinks.find(link => link.attributes && link.attributes['dd.kind'] === 'span-pointer' ) - assert.ok(pointerLink, 'Should have a span pointer link') assertObjectContains(pointerLink, { attributes: { diff --git a/packages/dd-trace/src/aiguard/sdk.js b/packages/dd-trace/src/aiguard/sdk.js index 20a6aa7a108..e53d8324c17 100644 --- a/packages/dd-trace/src/aiguard/sdk.js +++ b/packages/dd-trace/src/aiguard/sdk.js @@ -140,7 +140,7 @@ class AIGuard extends NoopAIGuard { } const { block = true } = opts ?? {} return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => { - const internalSpan = span?._span || span + span = span?._span || span const last = messages[messages.length - 1] const target = this.#isToolCall(last) ? 'tool' : 'prompt' span.setTag(AI_GUARD_TARGET_TAG_KEY, target) @@ -153,10 +153,10 @@ class AIGuard extends NoopAIGuard { const metaStruct = { messages: this.#buildMessagesForMetaStruct(messages), } - internalSpan.meta_struct = { + span.meta_struct = { [AI_GUARD_META_STRUCT_KEY]: metaStruct, } - const rootSpan = internalSpan.context()?._trace?.started?.[0] + const rootSpan = span.context()?._trace?.started?.[0] if (rootSpan) { // keepTrace must be called before executeRequest so the sampling decision // is propagated correctly to outgoing HTTP client calls. diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index b9ddfba08ce..a23ad6e2a0a 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -8,6 +8,25 @@ const logger = require('../log') const { getValueFromEnvSources } = require('../config/helper') const Span = require('../opentracing/span') const PublicSpan = require('../opentracing/public/span') + +/** + * Checks whether a value is a Span or PublicSpan instance. + * @param {unknown} value + * @returns {boolean} + */ +function isSpan (value) { + return value instanceof Span || value instanceof PublicSpan +} + +/** + * Returns the underlying raw DatadogSpan, unwrapping a PublicSpan if needed. + * @param {import('../opentracing/span') | PublicSpan} span + * @returns {import('../opentracing/span')} + */ +function unwrapSpan (span) { + return span instanceof PublicSpan ? span._span : span +} + const { SPAN_KIND, OUTPUT_VALUE, INPUT_VALUE } = require('./constants/tags') const { getFunctionArguments, @@ -210,7 +229,7 @@ class LLMObs extends NoopLLMObs { span = this._active() } - if ((span && !options) && !(span instanceof Span) && !(span instanceof PublicSpan)) { + if ((span && !options) && !isSpan(span)) { options = span span = this._active() } @@ -231,8 +250,7 @@ class LLMObs extends NoopLLMObs { err = 'invalid_span_type' throw new Error('Span must be an LLMObs-generated span') } - const rawSpan = span instanceof PublicSpan ? span._span : span - if (rawSpan._duration !== undefined) { + if (unwrapSpan(span)._duration !== undefined) { err = 'invalid_finished_span' throw new Error('Cannot annotate a finished span') } @@ -289,7 +307,7 @@ class LLMObs extends NoopLLMObs { err = 'no_active_span' throw new Error('No span provided and no active LLMObs-generated span found') } - if (!(span instanceof Span) && !(span instanceof PublicSpan)) { + if (!isSpan(span)) { err = 'invalid_span' throw new TypeError('Span must be a valid Span object.') } diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 21277f119d9..3bbf4df6cbc 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -40,8 +40,6 @@ const LLMObsTagger = require('./tagger') * Resolves the tagMap key for a raw span from the finish channel. * Plugin-instrumented spans are registered with raw Span instances, * while SDK spans are registered with PublicSpan wrappers. - * @param {import('../opentracing/span')} span - raw span from the finish channel - * @returns {import('../opentracing/span') | PublicSpan | undefined} */ function resolveTagMapKey (span) { if (LLMObsTagger.tagMap.has(span)) return span @@ -118,7 +116,6 @@ class LLMObsSpanProcessor { } format (span, tagMapKey) { - if (!tagMapKey) tagMapKey = resolveTagMapKey(span) const llmObsSpan = new LLMObservabilitySpan() let inputType, outputType @@ -175,7 +172,7 @@ class LLMObsSpanProcessor { const name = mlObsTags[NAME] || span._name - const tags = this.#getTags(tagMapKey, mlApp, sessionId, error) + const tags = this.#getTags(span, tagMapKey, mlApp, sessionId, error) llmObsSpan._tags = tags const processedSpan = this.#runProcessor(llmObsSpan) @@ -265,7 +262,7 @@ class LLMObsSpanProcessor { add(obj, carrier) } - #getTags (span, mlApp, sessionId, error) { + #getTags (span, tagMapKey, mlApp, sessionId, error) { let tags = { ...this.#config.parsedDdTags, version: this.#config.version, @@ -283,10 +280,10 @@ class LLMObsSpanProcessor { if (sessionId) tags.session_id = sessionId - const integration = LLMObsTagger.tagMap.get(span)?.[INTEGRATION] + const integration = LLMObsTagger.tagMap.get(tagMapKey)?.[INTEGRATION] if (integration) tags.integration = integration - const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {} + const existingTags = LLMObsTagger.tagMap.get(tagMapKey)?.[TAGS] || {} if (existingTags) tags = { ...tags, ...existingTags } return tags From 3a3991a65126bb1f18cd7b4fb451681815689743 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 7 Apr 2026 14:50:18 -0400 Subject: [PATCH 24/48] simplified small changes --- .../datadog-plugin-child_process/test/index.spec.js | 10 ++++------ packages/datadog-plugin-cypress/src/cypress-plugin.js | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/datadog-plugin-child_process/test/index.spec.js b/packages/datadog-plugin-child_process/test/index.spec.js index d59e2ede4fc..43ec0302621 100644 --- a/packages/datadog-plugin-child_process/test/index.spec.js +++ b/packages/datadog-plugin-child_process/test/index.spec.js @@ -511,7 +511,7 @@ describe('Child process plugin', () => { beforeEach((done) => { if (hasParentSpan) { - parentSpan = tracer.startSpan('parent') + parentSpan = tracer.startSpan('parent')._span parentSpan.finish() tracer.scope().activate(parentSpan, done) } else { @@ -545,11 +545,10 @@ describe('Child process plugin', () => { it('should maintain previous span after the execution', (done) => { const res = childProcess[methodName]('ls') const span = storage('legacy').getStore()?.span - const expectedParentSpan = parentSpan?._span || parentSpan - assert.strictEqual(span, expectedParentSpan) + assert.strictEqual(span, parentSpan) if (async) { res.on('close', () => { - assert.strictEqual(span, expectedParentSpan) + assert.strictEqual(span, parentSpan) done() }) } else { @@ -561,8 +560,7 @@ describe('Child process plugin', () => { it('should maintain previous span in the callback', (done) => { childProcess[methodName]('ls', () => { const span = storage('legacy').getStore()?.span - const expectedParentSpan = parentSpan?._span || parentSpan - assert.strictEqual(span, expectedParentSpan) + assert.strictEqual(span, parentSpan) done() }) }) diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 2cb4d487e9c..74b30ce3302 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -1047,11 +1047,10 @@ class CypressPlugin { if (isQuarantinedFromSupport) { this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true') } - const activeSpan = this.activeTestSpan._span || this.activeTestSpan const finishedTest = { testName, testStatus, - finishTime: activeSpan._getTime(), // we store the finish time here + finishTime: this.activeTestSpan._span._getTime(), // we store the finish time here testSpan: this.activeTestSpan, isEfdRetry, isAttemptToFix, From dfc4688c4e7ddba879e701b7a9c449a8aa78d2db Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 7 Apr 2026 15:27:29 -0400 Subject: [PATCH 25/48] removed space --- packages/datadog-plugin-cypress/src/cypress-plugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 74b30ce3302..184d5395dbb 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -1047,6 +1047,7 @@ class CypressPlugin { if (isQuarantinedFromSupport) { this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true') } + const finishedTest = { testName, testStatus, From 187727bb26a6fefa4187a0e678eff6b55a554d9a Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Thu, 9 Apr 2026 18:52:23 -0400 Subject: [PATCH 26/48] removed llmobs logic and used storage instead --- packages/dd-trace/src/llmobs/sdk.js | 52 +++++++------------ .../dd-trace/src/llmobs/span_processor.js | 37 ++++--------- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index a23ad6e2a0a..76a5a905f5d 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -7,25 +7,6 @@ const tracerVersion = require('../../../../package.json').version const logger = require('../log') const { getValueFromEnvSources } = require('../config/helper') const Span = require('../opentracing/span') -const PublicSpan = require('../opentracing/public/span') - -/** - * Checks whether a value is a Span or PublicSpan instance. - * @param {unknown} value - * @returns {boolean} - */ -function isSpan (value) { - return value instanceof Span || value instanceof PublicSpan -} - -/** - * Returns the underlying raw DatadogSpan, unwrapping a PublicSpan if needed. - * @param {import('../opentracing/span') | PublicSpan} span - * @returns {import('../opentracing/span')} - */ -function unwrapSpan (span) { - return span instanceof PublicSpan ? span._span : span -} const { SPAN_KIND, OUTPUT_VALUE, INPUT_VALUE } = require('./constants/tags') const { @@ -33,6 +14,7 @@ const { validateKind, } = require('./util') const { storage } = require('./storage') +const { storage: storageCore } = require('../../../datadog-core') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') @@ -49,16 +31,23 @@ class LLMObs extends NoopLLMObs { */ #hasUserSpanProcessor = false + /** + * @param {import('../tracer')} tracer - Tracer instance + * @param {import('./index')} llmobsModule - LLMObs module instance + * @param {import('../config/config-base')} config - Tracer configuration + */ constructor (tracer, llmobsModule, config) { super(tracer) + /** @type {import('../config/config-base')} */ this._config = config + this._llmobsModule = llmobsModule this._tagger = new LLMObsTagger(config) } get enabled () { - return this._config.llmobs.enabled + return this._config.llmobs.enabled ?? false } enable (options = {}) { @@ -76,13 +65,10 @@ class LLMObs extends NoopLLMObs { return } - const llmobs = { - mlApp: options.mlApp, - agentlessEnabled: options.agentlessEnabled, - } - // TODO: This will update config telemetry with the origin 'code', which is not ideal when `enable()` is called - // based on `APM_TRACING` RC product updates. - this._config.updateOptions({ llmobs }) + // TODO: These configs should be passed through directly at construction time instead. + this._config.llmobs.enabled = true + this._config.llmobs.mlApp = options.mlApp + this._config.llmobs.agentlessEnabled = options.agentlessEnabled // configure writers and channel subscribers this._llmobsModule.enable(this._config) @@ -126,12 +112,12 @@ class LLMObs extends NoopLLMObs { if (fn.length > 1) { return this._tracer.trace(name, spanOptions, (span, cb) => - this._activate(span, { kind, ...llmobsOptions }, () => fn(span, cb)) + this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span, cb)) ) } return this._tracer.trace(name, spanOptions, span => - this._activate(span, { kind, ...llmobsOptions }, () => fn(span)) + this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span)) ) } @@ -159,7 +145,7 @@ class LLMObs extends NoopLLMObs { function wrapped () { telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: false, kind }) - const span = llmobs._tracer.scope().active() + const span = storageCore('legacy').getStore()?.span const fnArgs = arguments const lastArgId = fnArgs.length - 1 @@ -229,7 +215,7 @@ class LLMObs extends NoopLLMObs { span = this._active() } - if ((span && !options) && !isSpan(span)) { + if ((span && !options) && !(span instanceof Span)) { options = span span = this._active() } @@ -250,7 +236,7 @@ class LLMObs extends NoopLLMObs { err = 'invalid_span_type' throw new Error('Span must be an LLMObs-generated span') } - if (unwrapSpan(span)._duration !== undefined) { + if (span._duration !== undefined) { err = 'invalid_finished_span' throw new Error('Cannot annotate a finished span') } @@ -307,7 +293,7 @@ class LLMObs extends NoopLLMObs { err = 'no_active_span' throw new Error('No span provided and no active LLMObs-generated span found') } - if (!isSpan(span)) { + if (!(span instanceof Span)) { err = 'invalid_span' throw new TypeError('Span must be a valid Span object.') } diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 3bbf4df6cbc..6eb85fd85b3 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -9,7 +9,6 @@ const { ERROR_TYPE, ERROR_STACK, } = require('../constants') -const PublicSpan = require('../opentracing/public/span') const { SPAN_KIND, MODEL_NAME, @@ -36,16 +35,6 @@ const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') -/** - * Resolves the tagMap key for a raw span from the finish channel. - * Plugin-instrumented spans are registered with raw Span instances, - * while SDK spans are registered with PublicSpan wrappers. - */ -function resolveTagMapKey (span) { - if (LLMObsTagger.tagMap.has(span)) return span - const wrapped = new PublicSpan(span) - if (LLMObsTagger.tagMap.has(wrapped)) return wrapped -} class LLMObservabilitySpan { constructor () { @@ -61,7 +50,7 @@ class LLMObservabilitySpan { } class LLMObsSpanProcessor { - /** @type {import('../config')} */ + /** @type {import('../config/config-base')} */ #config /** @type {((span: LLMObservabilitySpan) => LLMObservabilitySpan | null) | null} */ @@ -85,19 +74,15 @@ class LLMObsSpanProcessor { // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation? process (span) { if (!this.#config.llmobs.enabled) return - // The finish channel always publishes raw Span instances, but the tagMap - // may be keyed by either a raw Span (plugin-instrumented) or a PublicSpan - // wrapper (SDK-instrumented). Resolve to whichever key is present. - const tagMapKey = resolveTagMapKey(span) // if the span is not in our private tagger map, it is not an llmobs span - if (!tagMapKey) return + if (!LLMObsTagger.tagMap.has(span)) return try { - const formattedEvent = this.format(span, tagMapKey) - telemetry.incrementLLMObsSpanFinishedCount(tagMapKey) + const formattedEvent = this.format(span) + telemetry.incrementLLMObsSpanFinishedCount(span) if (formattedEvent == null) return - const mlObsTags = LLMObsTagger.tagMap.get(tagMapKey) + const mlObsTags = LLMObsTagger.tagMap.get(span) const routing = { apiKey: mlObsTags[ROUTING_API_KEY], site: mlObsTags[ROUTING_SITE], @@ -115,12 +100,12 @@ class LLMObsSpanProcessor { } } - format (span, tagMapKey) { + format (span) { const llmObsSpan = new LLMObservabilitySpan() let inputType, outputType const spanTags = span.context()._tags - const mlObsTags = LLMObsTagger.tagMap.get(tagMapKey) + const mlObsTags = LLMObsTagger.tagMap.get(span) const spanKind = mlObsTags[SPAN_KIND] @@ -172,7 +157,7 @@ class LLMObsSpanProcessor { const name = mlObsTags[NAME] || span._name - const tags = this.#getTags(span, tagMapKey, mlApp, sessionId, error) + const tags = this.#getTags(span, mlApp, sessionId, error) llmObsSpan._tags = tags const processedSpan = this.#runProcessor(llmObsSpan) @@ -262,7 +247,7 @@ class LLMObsSpanProcessor { add(obj, carrier) } - #getTags (span, tagMapKey, mlApp, sessionId, error) { + #getTags (span, mlApp, sessionId, error) { let tags = { ...this.#config.parsedDdTags, version: this.#config.version, @@ -280,10 +265,10 @@ class LLMObsSpanProcessor { if (sessionId) tags.session_id = sessionId - const integration = LLMObsTagger.tagMap.get(tagMapKey)?.[INTEGRATION] + const integration = LLMObsTagger.tagMap.get(span)?.[INTEGRATION] if (integration) tags.integration = integration - const existingTags = LLMObsTagger.tagMap.get(tagMapKey)?.[TAGS] || {} + const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {} if (existingTags) tags = { ...tags, ...existingTags } return tags From 1d75fbd13287df0380c9713279632a97d309e686 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Thu, 9 Apr 2026 19:42:47 -0400 Subject: [PATCH 27/48] remove scope file --- .../dd-trace/src/opentracing/public/scope.js | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 packages/dd-trace/src/opentracing/public/scope.js diff --git a/packages/dd-trace/src/opentracing/public/scope.js b/packages/dd-trace/src/opentracing/public/scope.js deleted file mode 100644 index a9b6a123cd0..00000000000 --- a/packages/dd-trace/src/opentracing/public/scope.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const PublicSpan = require('./span') - -class Scope { - constructor (scope) { - this._scope = scope - } - - active () { - const span = this._scope.active() - return span ? new PublicSpan(span) : null - } - - activate (span, fn) { - return this._scope.activate(span?._span || span, fn) - } - - bind (fn, span) { - return this._scope.bind(fn, span?._span || span) - } -} - -module.exports = Scope From 0ecd85829242958aee341c9a1b0b5934412ac8ef Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Thu, 9 Apr 2026 22:08:07 -0400 Subject: [PATCH 28/48] unwrap span on internal methods --- packages/dd-trace/src/llmobs/sdk.js | 12 +++-- .../dd-trace/test/llmobs/sdk/index.spec.js | 52 +++++++++---------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index 76a5a905f5d..a8deb638240 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -8,13 +8,13 @@ const logger = require('../log') const { getValueFromEnvSources } = require('../config/helper') const Span = require('../opentracing/span') +const { storage: storageCore } = require('../../../datadog-core') const { SPAN_KIND, OUTPUT_VALUE, INPUT_VALUE } = require('./constants/tags') const { getFunctionArguments, validateKind, } = require('./util') const { storage } = require('./storage') -const { storage: storageCore } = require('../../../datadog-core') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') @@ -112,12 +112,12 @@ class LLMObs extends NoopLLMObs { if (fn.length > 1) { return this._tracer.trace(name, spanOptions, (span, cb) => - this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span, cb)) + this._activate(span, { kind, ...llmobsOptions }, () => fn(span, cb)) ) } return this._tracer.trace(name, spanOptions, span => - this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span)) + this._activate(span, { kind, ...llmobsOptions }, () => fn(span)) ) } @@ -215,6 +215,8 @@ class LLMObs extends NoopLLMObs { span = this._active() } + span = span?._span || span + if ((span && !options) && !(span instanceof Span)) { options = span span = this._active() @@ -287,6 +289,7 @@ class LLMObs extends NoopLLMObs { exportSpan (span) { span = span || this._active() + span = span?._span || span let err = '' try { if (!span) { @@ -527,13 +530,14 @@ class LLMObs extends NoopLLMObs { } _activate (span, options, fn) { + span = span?._span || span const parentStore = storage.getStore() if (this.enabled) storage.enterWith({ ...parentStore, span }) if (options) { this._tagger.registerLLMObsSpan(span, { ...options, - parent: parentStore?.span, + parent: parentStore?.span?._span || parentStore?.span, }) } diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index 74a5d0d90dd..c3a081465ff 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -277,7 +277,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'workflow', name: 'test' }, outerLLMSpan => { llmobs.trace({ kind: 'task', name: 'test' }, innerLLMSpan => { assert.strictEqual( - LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'], + LLMObsTagger.tagMap.get(innerLLMSpan._span)['_ml_obs.llmobs_parent_id'], outerLLMSpan.context().toSpanId() ) // TODO: need to implement custom trace IDs @@ -289,15 +289,15 @@ describe('sdk', () => { it('maintains llmobs parentage separately from apm spans', () => { llmobs.trace({ kind: 'workflow', name: 'outer-llm' }, outerLLMSpan => { - assert.strictEqual(llmobs._active(), outerLLMSpan) + assert.strictEqual(llmobs._active(), outerLLMSpan._span) tracer.trace('apmSpan', apmSpan => { - assert.strictEqual(llmobs._active(), outerLLMSpan) + assert.strictEqual(llmobs._active(), outerLLMSpan._span) llmobs.trace({ kind: 'workflow', name: 'inner-llm' }, innerLLMSpan => { - assert.strictEqual(llmobs._active(), innerLLMSpan) + assert.strictEqual(llmobs._active(), innerLLMSpan._span) // llmobs span linkage assert.strictEqual( - LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'], + LLMObsTagger.tagMap.get(innerLLMSpan._span)['_ml_obs.llmobs_parent_id'], outerLLMSpan.context().toSpanId() ) @@ -331,16 +331,16 @@ describe('sdk', () => { it('maintains the llmobs parentage when error callbacks are used', () => { llmobs.trace({ kind: 'workflow' }, outer => { llmobs.trace({ kind: 'task' }, (inner, cb) => { - assert.strictEqual(llmobs._active(), inner) - assert.strictEqual(LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) + assert.strictEqual(llmobs._active(), inner._span) + assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) cb() // finish the span }) - assert.strictEqual(llmobs._active(), outer) + assert.strictEqual(llmobs._active(), outer._span) llmobs.trace({ kind: 'task' }, (inner) => { - assert.strictEqual(llmobs._active(), inner) - assert.strictEqual(LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) + assert.strictEqual(llmobs._active(), inner._span) + assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) }) }) }) @@ -359,7 +359,7 @@ describe('sdk', () => { span = _span }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'workflow', '_ml_obs.meta.ml_app': 'override', '_ml_obs.meta.model_name': 'modelName', @@ -710,7 +710,7 @@ describe('sdk', () => { function outerLLMObs () { outerLLMObsSpan = llmobs._active() - assert.strictEqual(outerLLMObsSpan, tracer.scope().active()) + assert.strictEqual(outerLLMObsSpan, tracer.scope().active()._span) apmWrapped() } @@ -720,7 +720,7 @@ describe('sdk', () => { } function innerLLMObs () { innerLLMObsSpan = llmobs._active() - assert.strictEqual(innerLLMObsSpan, tracer.scope().active()) + assert.strictEqual(innerLLMObsSpan, tracer.scope().active()._span) assert.strictEqual( LLMObsTagger.tagMap.get(innerLLMObsSpan)['_ml_obs.llmobs_parent_id'], outerLLMObsSpan.context().toSpanId() @@ -768,12 +768,12 @@ describe('sdk', () => { function outer () { outerSpan = llmobs._active() wrappedInner1(() => {}) - assert.strictEqual(outerSpan, tracer.scope().active()) + assert.strictEqual(outerSpan, tracer.scope().active()._span) wrappedInner2() } function inner1 (cb) { - const inner = tracer.scope().active() + const inner = tracer.scope().active()._span assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], @@ -783,7 +783,7 @@ describe('sdk', () => { } function inner2 () { - const inner = tracer.scope().active() + const inner = tracer.scope().active()._span assert.strictEqual(llmobs._active(), inner) assert.strictEqual( LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], @@ -848,7 +848,7 @@ describe('sdk', () => { assert.throws(() => llmobs.annotate(span)) // span should still exist in the registry, just with no annotations - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -883,7 +883,7 @@ describe('sdk', () => { // TODO this might end up being obsolete with llmobs span kind as optional sinon.spy(llmobs._tagger, 'tagLLMIO') llmobs.trace({ kind: 'llm', name: 'test' }, span => { - LLMObsTagger.tagMap.get(span)['_ml_obs.meta.span.kind'] = undefined // somehow this is set + LLMObsTagger.tagMap.get(span._span)['_ml_obs.meta.span.kind'] = undefined // somehow this is set assert.throws(() => llmobs.annotate(span, {})) }) @@ -898,7 +898,7 @@ describe('sdk', () => { const inputData = {} llmobs.annotate({ inputData }) - sinon.assert.calledWith(llmobs._tagger.tagTextIO, span, inputData, undefined) + sinon.assert.calledWith(llmobs._tagger.tagTextIO, span._span, inputData, undefined) }) llmobs._tagger.tagTextIO.restore() @@ -912,7 +912,7 @@ describe('sdk', () => { const inputData = {} llmobs.annotate({ inputData }) - sinon.assert.calledWith(llmobs._tagger.tagTextIO, llmobsSpan, inputData, undefined) + sinon.assert.calledWith(llmobs._tagger.tagTextIO, llmobsSpan._span, inputData, undefined) }) }) @@ -926,7 +926,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -943,7 +943,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'embedding', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'embedding', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -960,7 +960,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'retrieval', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'retrieval', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -976,7 +976,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ metadata }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -991,7 +991,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ metrics }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -1006,7 +1006,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ tags }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', From d8be3e02cc17f20de26a50e48a54eabc623c0f26 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Thu, 9 Apr 2026 22:13:59 -0400 Subject: [PATCH 29/48] fix linter --- packages/dd-trace/src/llmobs/span_processor.js | 1 - packages/dd-trace/test/llmobs/sdk/index.spec.js | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 6eb85fd85b3..9bf7cd3bd80 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -35,7 +35,6 @@ const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') - class LLMObservabilitySpan { constructor () { this.input = [] diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index c3a081465ff..5a0fa8e1c2f 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -332,7 +332,8 @@ describe('sdk', () => { llmobs.trace({ kind: 'workflow' }, outer => { llmobs.trace({ kind: 'task' }, (inner, cb) => { assert.strictEqual(llmobs._active(), inner._span) - assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) + assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], + outer.context().toSpanId()) cb() // finish the span }) @@ -340,7 +341,8 @@ describe('sdk', () => { llmobs.trace({ kind: 'task' }, (inner) => { assert.strictEqual(llmobs._active(), inner._span) - assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) + assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], + outer.context().toSpanId()) }) }) }) From 5b097d9b9e1110ba106d3d6e1bdb16f40d346330 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 10 Apr 2026 12:58:12 -0400 Subject: [PATCH 30/48] add TODO --- packages/datadog-plugin-cypress/src/cypress-plugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 505eba5ea84..7ba7a3ca63c 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -1103,6 +1103,7 @@ class CypressPlugin { const finishedTest = { testName, testStatus, + // TODO: removed the need of accessing private span internals finishTime: this.activeTestSpan._span._getTime(), // we store the finish time here testSpan: this.activeTestSpan, isEfdRetry, From c9c14632e501cb50ca96ce6a0aec6690019c26f8 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 10 Apr 2026 15:50:42 -0400 Subject: [PATCH 31/48] removed span fallback --- packages/dd-trace/src/scope.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index d03961b0af1..69656187fad 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -18,7 +18,7 @@ class Scope { activate (span, callback) { if (typeof callback !== 'function') return callback - span = span?._span || span + span = span?._span || null const oldStore = storage('legacy').getStore() const newStore = span ? storage('legacy').getStore(span._store) : oldStore @@ -41,7 +41,7 @@ class Scope { bind (fn, span) { if (typeof fn !== 'function') return fn - span = span?._span || span + span = span?._span || null const scope = this const spanOrActive = this._spanOrActive(span) From 941e32fa0030eb589edc2d82326e270585237ce7 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 10 Apr 2026 16:30:26 -0400 Subject: [PATCH 32/48] added back fallback --- packages/dd-trace/src/scope.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index 69656187fad..4fd7fbd11ff 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -18,7 +18,9 @@ class Scope { activate (span, callback) { if (typeof callback !== 'function') return callback - span = span?._span || null + // We need the span fallback because Otel (e.g. the OTel context manager) + // passes raw DDSpans that which have no ._span property. + span = span?._span || span const oldStore = storage('legacy').getStore() const newStore = span ? storage('legacy').getStore(span._store) : oldStore @@ -41,7 +43,9 @@ class Scope { bind (fn, span) { if (typeof fn !== 'function') return fn - span = span?._span || null + // We need the span fallback because Otel (e.g. the OTel context manager) + // passes raw DDSpans that which have no ._span property. + span = span?._span || span const scope = this const spanOrActive = this._spanOrActive(span) From 4583583b5815c4bb05c1c376b530ac938db39b9a Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 10 Apr 2026 17:07:00 -0400 Subject: [PATCH 33/48] added internal annotate --- packages/dd-trace/src/llmobs/sdk.js | 95 +++++++++++++++-------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index a8deb638240..c90a524a276 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -112,12 +112,12 @@ class LLMObs extends NoopLLMObs { if (fn.length > 1) { return this._tracer.trace(name, spanOptions, (span, cb) => - this._activate(span, { kind, ...llmobsOptions }, () => fn(span, cb)) + this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span, cb)) ) } return this._tracer.trace(name, spanOptions, span => - this._activate(span, { kind, ...llmobsOptions }, () => fn(span)) + this._activate(span._span, { kind, ...llmobsOptions }, () => fn(span)) ) } @@ -208,18 +208,16 @@ class LLMObs extends NoopLLMObs { return this._tracer.wrap(name, spanOptions, wrapped) } - annotate (span, options, autoinstrumented = false) { + annotate (span, options) { if (!this.enabled) return if (!span) { span = this._active() - } - - span = span?._span || span - - if ((span && !options) && !(span instanceof Span)) { + } else if (!options && !(span?._span instanceof Span)) { options = span span = this._active() + } else { + span = span._span } let err = '' @@ -243,53 +241,55 @@ class LLMObs extends NoopLLMObs { throw new Error('Cannot annotate a finished span') } - const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND] - if (!spanKind) { - err = 'invalid_no_span_kind' - throw new Error('LLMObs span must have a span kind specified') - } - - const { inputData, outputData, metadata, metrics, tags, prompt } = options - - if (inputData || outputData) { - if (spanKind === 'llm') { - this._tagger.tagLLMIO(span, inputData, outputData) - } else if (spanKind === 'embedding') { - this._tagger.tagEmbeddingIO(span, inputData, outputData) - } else if (spanKind === 'retrieval') { - this._tagger.tagRetrievalIO(span, inputData, outputData) - } else { - this._tagger.tagTextIO(span, inputData, outputData) - } - } - - if (metadata) { - this._tagger.tagMetadata(span, metadata) - } - if (metrics) { - this._tagger.tagMetrics(span, metrics) - } - if (tags) { - this._tagger.tagSpanTags(span, tags) - } - if (prompt) { - this._tagger.tagPrompt(span, prompt) - } + this._annotate(span, options) } catch (e) { if (e.ddErrorTag) { err = e.ddErrorTag } throw e } finally { - if (autoinstrumented === false) { - telemetry.recordLLMObsAnnotate(span, err) + telemetry.recordLLMObsAnnotate(span, err) + } + } + + _annotate (span, options) { + const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND] + if (!spanKind) { + const err = new Error('LLMObs span must have a span kind specified') + err.ddErrorTag = 'invalid_no_span_kind' + throw err + } + + const { inputData, outputData, metadata, metrics, tags, prompt } = options + + if (inputData || outputData) { + if (spanKind === 'llm') { + this._tagger.tagLLMIO(span, inputData, outputData) + } else if (spanKind === 'embedding') { + this._tagger.tagEmbeddingIO(span, inputData, outputData) + } else if (spanKind === 'retrieval') { + this._tagger.tagRetrievalIO(span, inputData, outputData) + } else { + this._tagger.tagTextIO(span, inputData, outputData) } } + + if (metadata) { + this._tagger.tagMetadata(span, metadata) + } + if (metrics) { + this._tagger.tagMetrics(span, metrics) + } + if (tags) { + this._tagger.tagSpanTags(span, tags) + } + if (prompt) { + this._tagger.tagPrompt(span, prompt) + } } exportSpan (span) { - span = span || this._active() - span = span?._span || span + span = span ? span._span : this._active() let err = '' try { if (!span) { @@ -521,7 +521,9 @@ class LLMObs extends NoopLLMObs { annotations.outputData = output } - this.annotate(span, annotations, true) + if ((annotations.inputData || annotations.outputData) && LLMObsTagger.tagMap.has(span)) { + this._annotate(span, annotations) + } } _active () { @@ -530,14 +532,13 @@ class LLMObs extends NoopLLMObs { } _activate (span, options, fn) { - span = span?._span || span const parentStore = storage.getStore() if (this.enabled) storage.enterWith({ ...parentStore, span }) if (options) { this._tagger.registerLLMObsSpan(span, { ...options, - parent: parentStore?.span?._span || parentStore?.span, + parent: parentStore?.span, }) } From 149f328db972e91642835ce3b295b2d14df4c471 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Fri, 10 Apr 2026 17:34:11 -0400 Subject: [PATCH 34/48] retrying fallback with fix --- .../datadog-plugin-child_process/test/index.spec.js | 8 ++++---- packages/dd-trace/src/opentelemetry/context_manager.js | 3 ++- packages/dd-trace/src/scope.js | 10 ++++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/datadog-plugin-child_process/test/index.spec.js b/packages/datadog-plugin-child_process/test/index.spec.js index 43ec0302621..1a1180e8451 100644 --- a/packages/datadog-plugin-child_process/test/index.spec.js +++ b/packages/datadog-plugin-child_process/test/index.spec.js @@ -511,7 +511,7 @@ describe('Child process plugin', () => { beforeEach((done) => { if (hasParentSpan) { - parentSpan = tracer.startSpan('parent')._span + parentSpan = tracer.startSpan('parent') parentSpan.finish() tracer.scope().activate(parentSpan, done) } else { @@ -545,10 +545,10 @@ describe('Child process plugin', () => { it('should maintain previous span after the execution', (done) => { const res = childProcess[methodName]('ls') const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan) + assert.strictEqual(span, parentSpan?._span) if (async) { res.on('close', () => { - assert.strictEqual(span, parentSpan) + assert.strictEqual(span, parentSpan?._span) done() }) } else { @@ -560,7 +560,7 @@ describe('Child process plugin', () => { it('should maintain previous span in the callback', (done) => { childProcess[methodName]('ls', () => { const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan) + assert.strictEqual(span, parentSpan?._span) done() }) }) diff --git a/packages/dd-trace/src/opentelemetry/context_manager.js b/packages/dd-trace/src/opentelemetry/context_manager.js index e0999454f45..251b221c00a 100644 --- a/packages/dd-trace/src/opentelemetry/context_manager.js +++ b/packages/dd-trace/src/opentelemetry/context_manager.js @@ -5,6 +5,7 @@ const { storage } = require('../../../datadog-core') const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage') const tracer = require('../../') +const PublicSpan = require('../opentracing/public/span') const SpanContext = require('./span_context') class ContextManager { @@ -75,7 +76,7 @@ class ContextManager { for (const baggage of baggageItems) { setBaggageItem(baggage[0], baggage[1].value) } - if (span && span._ddSpan) return ddScope.activate(span._ddSpan, run) + if (span && span._ddSpan) return ddScope.activate(new PublicSpan(span._ddSpan), run) return run() } diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index 4fd7fbd11ff..88c37825051 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -18,9 +18,8 @@ class Scope { activate (span, callback) { if (typeof callback !== 'function') return callback - // We need the span fallback because Otel (e.g. the OTel context manager) - // passes raw DDSpans that which have no ._span property. - span = span?._span || span + // Internal plugin utilities (e.g. web.js) pass raw DatadogSpans directly. + span = span?._span || null const oldStore = storage('legacy').getStore() const newStore = span ? storage('legacy').getStore(span._store) : oldStore @@ -43,9 +42,8 @@ class Scope { bind (fn, span) { if (typeof fn !== 'function') return fn - // We need the span fallback because Otel (e.g. the OTel context manager) - // passes raw DDSpans that which have no ._span property. - span = span?._span || span + // Internal plugin utilities (e.g. web.js) pass raw DatadogSpans directly. + span = span?._span || null const scope = this const spanOrActive = this._spanOrActive(span) From 8f054727c10a3e515ac04a1508ffafb180fc8533 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 12 Apr 2026 22:13:24 -0400 Subject: [PATCH 35/48] remove use of internal span on tests --- packages/dd-trace/src/aiguard/sdk.js | 2 +- packages/dd-trace/src/scope.js | 6 +--- .../dd-trace/test/plugins/log_plugin.spec.js | 3 +- packages/dd-trace/test/scope.spec.js | 30 +++++++++---------- packages/dd-trace/test/tracer.spec.js | 7 +++-- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/dd-trace/src/aiguard/sdk.js b/packages/dd-trace/src/aiguard/sdk.js index 515569378c4..a7a27a1ade0 100644 --- a/packages/dd-trace/src/aiguard/sdk.js +++ b/packages/dd-trace/src/aiguard/sdk.js @@ -144,7 +144,7 @@ class AIGuard extends NoopAIGuard { } const { block = true } = opts ?? {} return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => { - span = span?._span || span + span = span._span const last = messages[messages.length - 1] const target = this.#isToolCall(last) ? 'tool' : 'prompt' span.setTag(AI_GUARD_TARGET_TAG_KEY, target) diff --git a/packages/dd-trace/src/scope.js b/packages/dd-trace/src/scope.js index 88c37825051..56e2224cab2 100644 --- a/packages/dd-trace/src/scope.js +++ b/packages/dd-trace/src/scope.js @@ -18,8 +18,7 @@ class Scope { activate (span, callback) { if (typeof callback !== 'function') return callback - // Internal plugin utilities (e.g. web.js) pass raw DatadogSpans directly. - span = span?._span || null + span = span?._span const oldStore = storage('legacy').getStore() const newStore = span ? storage('legacy').getStore(span._store) : oldStore @@ -42,9 +41,6 @@ class Scope { bind (fn, span) { if (typeof fn !== 'function') return fn - // Internal plugin utilities (e.g. web.js) pass raw DatadogSpans directly. - span = span?._span || null - const scope = this const spanOrActive = this._spanOrActive(span) diff --git a/packages/dd-trace/test/plugins/log_plugin.spec.js b/packages/dd-trace/test/plugins/log_plugin.spec.js index efc83fd1fd8..2d4cde4476a 100644 --- a/packages/dd-trace/test/plugins/log_plugin.spec.js +++ b/packages/dd-trace/test/plugins/log_plugin.spec.js @@ -9,6 +9,7 @@ const { assertObjectContains } = require('../../../../integration-tests/helpers' require('../setup/core') const LogPlugin = require('../../src/plugins/log_plugin') const Tracer = require('../../src/tracer') +const PublicSpan = require('../../src/opentracing/public/span') const getConfig = require('../../src/config') const testLogChannel = channel('apm:test:log') @@ -51,7 +52,7 @@ describe('LogPlugin', () => { }) it('should include trace_id and span_id when a span is active', () => { - const span = tracer.startSpan('test') + const span = new PublicSpan(tracer.startSpan('test')) tracer.scope().activate(span, () => { const data = { message: {} } diff --git a/packages/dd-trace/test/scope.spec.js b/packages/dd-trace/test/scope.spec.js index de04d3d38a2..1aac37bbd53 100644 --- a/packages/dd-trace/test/scope.spec.js +++ b/packages/dd-trace/test/scope.spec.js @@ -6,6 +6,7 @@ const { describe, it, beforeEach } = require('mocha') const sinon = require('sinon') const { Span } = require('../../../vendor/dist/opentracing') +const PublicSpan = require('../src/opentracing/public/span') require('./setup/core') const Scope = require('../src/scope') @@ -15,7 +16,7 @@ describe('Scope', () => { beforeEach(() => { scope = new Scope() - span = new Span() + span = new PublicSpan(new Span()) }) describe('active()', () => { @@ -26,8 +27,7 @@ describe('Scope', () => { it('should return a PublicSpan wrapping the active span', () => { scope.activate(span, () => { const active = scope.active() - assert.notStrictEqual(active, span) - assert.strictEqual(active._span, span) + assert.strictEqual(active, span) }) }) }) @@ -53,7 +53,7 @@ describe('Scope', () => { assert.strictEqual(scope.active(), null) scope.activate(span, () => { - assert.strictEqual(scope.active()._span, span) + assert.strictEqual(scope.active(), span) }) assert.strictEqual(scope.active(), null) @@ -63,7 +63,7 @@ describe('Scope', () => { scope.activate(span, () => { const publicSpan = scope.active() scope.activate(publicSpan, () => { - assert.strictEqual(scope.active()._span, span) + assert.strictEqual(scope.active(), span) }) }) }) @@ -71,7 +71,7 @@ describe('Scope', () => { it('should persist through setTimeout', done => { scope.activate(span, () => { setTimeout(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) done() }, 0) }) @@ -80,7 +80,7 @@ describe('Scope', () => { it('should persist through setImmediate', done => { scope.activate(span, () => { setImmediate(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) done() }, 0) }) @@ -91,7 +91,7 @@ describe('Scope', () => { let shouldReturn = false const timer = setInterval(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) if (shouldReturn) { clearInterval(timer) @@ -106,7 +106,7 @@ describe('Scope', () => { it('should persist through process.nextTick', done => { scope.activate(span, () => { process.nextTick(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) done() }, 0) }) @@ -117,7 +117,7 @@ describe('Scope', () => { return scope.activate(span, () => { return promise.then(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) }) }) }) @@ -125,7 +125,7 @@ describe('Scope', () => { it('should handle concurrency', done => { scope.activate(span, () => { setImmediate(() => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) done() }) }) @@ -136,14 +136,14 @@ describe('Scope', () => { it('should handle errors', () => { const error = new Error('boom') - sinon.spy(span, 'setTag') + sinon.spy(span._span, 'setTag') try { scope.activate(span, () => { throw error }) } catch (e) { - sinon.assert.calledWith(span.setTag, 'error', e) + sinon.assert.calledWith(span._span.setTag, 'error', e) } }) }) @@ -152,7 +152,7 @@ describe('Scope', () => { describe('with a function', () => { it('should bind the function to the active span', () => { let fn = () => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) } scope.activate(span, () => { @@ -164,7 +164,7 @@ describe('Scope', () => { it('should bind the function to the provided span', () => { let fn = () => { - assert.strictEqual(scope.active()?._span, span) + assert.strictEqual(scope.active(), span) } fn = scope.bind(fn, span) diff --git a/packages/dd-trace/test/tracer.spec.js b/packages/dd-trace/test/tracer.spec.js index 970b56527e5..4627c021702 100644 --- a/packages/dd-trace/test/tracer.spec.js +++ b/packages/dd-trace/test/tracer.spec.js @@ -9,6 +9,7 @@ const { assertObjectContains } = require('../../../integration-tests/helpers') require('./setup/core') const Tracer = require('../src/tracer') const Span = require('../src/opentracing/span') +const PublicSpan = require('../src/opentracing/public/span') const getConfig = require('../src/config') const tags = require('../../../ext/tags') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') @@ -108,7 +109,7 @@ describe('Tracer', () => { }) it('should start the span as a child of the active span', () => { - const childOf = tracer.startSpan('parent') + const childOf = new PublicSpan(tracer.startSpan('parent')) tracer.scope().activate(childOf, () => { tracer.trace('name', {}, span => { @@ -118,8 +119,8 @@ describe('Tracer', () => { }) it('should allow overriding the parent span', () => { - const root = tracer.startSpan('root') - const childOf = tracer.startSpan('parent') + const root = new PublicSpan(tracer.startSpan('root')) + const childOf = new PublicSpan(tracer.startSpan('parent')) tracer.scope().activate(root, () => { tracer.trace('name', { childOf }, span => { From efbae5d2d44957d0af03b47d7f4a7caf7cc4eb64 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 12 Apr 2026 23:15:14 -0400 Subject: [PATCH 36/48] fix typo --- packages/datadog-plugin-cypress/src/cypress-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 7ba7a3ca63c..0fc5f12f24f 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -1103,7 +1103,7 @@ class CypressPlugin { const finishedTest = { testName, testStatus, - // TODO: removed the need of accessing private span internals + // TODO: remove the need of accessing private span internals finishTime: this.activeTestSpan._span._getTime(), // we store the finish time here testSpan: this.activeTestSpan, isEfdRetry, From 1d2dad35cd863594724f5226bb3c5e9b4e19ce53 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Sun, 12 Apr 2026 23:29:44 -0400 Subject: [PATCH 37/48] removed or in otel context manager --- packages/dd-trace/src/opentelemetry/context_manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/dd-trace/src/opentelemetry/context_manager.js b/packages/dd-trace/src/opentelemetry/context_manager.js index 251b221c00a..93236078268 100644 --- a/packages/dd-trace/src/opentelemetry/context_manager.js +++ b/packages/dd-trace/src/opentelemetry/context_manager.js @@ -17,8 +17,7 @@ class ContextManager { active () { const store = this._store.getStore() const baseContext = store || ROOT_CONTEXT - const publicSpan = tracer.scope().active() - const activeSpan = publicSpan?._span || publicSpan + const activeSpan = tracer.scope().active()?._span const storedSpan = store ? trace.getSpan(store) : null From 45ec521639539e77dd0a6af466b3ae8d9109c452 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 00:10:37 -0400 Subject: [PATCH 38/48] simplified tagMap.get test changes --- .../dd-trace/test/llmobs/sdk/index.spec.js | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index 5a0fa8e1c2f..f90802036fa 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -56,6 +56,9 @@ describe('sdk', () => { clock = sinon.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'], }) + const tagMap = LLMObsTagger.tagMap + const originalGet = tagMap.get.bind(tagMap) + tagMap.get = (span) => originalGet(span?._span ?? span) }) afterEach(() => { @@ -277,7 +280,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'workflow', name: 'test' }, outerLLMSpan => { llmobs.trace({ kind: 'task', name: 'test' }, innerLLMSpan => { assert.strictEqual( - LLMObsTagger.tagMap.get(innerLLMSpan._span)['_ml_obs.llmobs_parent_id'], + LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'], outerLLMSpan.context().toSpanId() ) // TODO: need to implement custom trace IDs @@ -297,7 +300,7 @@ describe('sdk', () => { // llmobs span linkage assert.strictEqual( - LLMObsTagger.tagMap.get(innerLLMSpan._span)['_ml_obs.llmobs_parent_id'], + LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'], outerLLMSpan.context().toSpanId() ) @@ -332,7 +335,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'workflow' }, outer => { llmobs.trace({ kind: 'task' }, (inner, cb) => { assert.strictEqual(llmobs._active(), inner._span) - assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], + assert.strictEqual(LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) cb() // finish the span }) @@ -341,7 +344,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'task' }, (inner) => { assert.strictEqual(llmobs._active(), inner._span) - assert.strictEqual(LLMObsTagger.tagMap.get(inner._span)['_ml_obs.llmobs_parent_id'], + assert.strictEqual(LLMObsTagger.tagMap.get(inner)['_ml_obs.llmobs_parent_id'], outer.context().toSpanId()) }) }) @@ -361,7 +364,7 @@ describe('sdk', () => { span = _span }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'workflow', '_ml_obs.meta.ml_app': 'override', '_ml_obs.meta.model_name': 'modelName', @@ -850,7 +853,7 @@ describe('sdk', () => { assert.throws(() => llmobs.annotate(span)) // span should still exist in the registry, just with no annotations - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -885,7 +888,7 @@ describe('sdk', () => { // TODO this might end up being obsolete with llmobs span kind as optional sinon.spy(llmobs._tagger, 'tagLLMIO') llmobs.trace({ kind: 'llm', name: 'test' }, span => { - LLMObsTagger.tagMap.get(span._span)['_ml_obs.meta.span.kind'] = undefined // somehow this is set + LLMObsTagger.tagMap.get(span)['_ml_obs.meta.span.kind'] = undefined // somehow this is set assert.throws(() => llmobs.annotate(span, {})) }) @@ -928,7 +931,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -945,7 +948,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'embedding', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'embedding', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -962,7 +965,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'retrieval', name: 'test' }, span => { llmobs.annotate({ inputData, outputData }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'retrieval', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -978,7 +981,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ metadata }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -993,7 +996,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ metrics }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', @@ -1008,7 +1011,7 @@ describe('sdk', () => { llmobs.trace({ kind: 'llm', name: 'test' }, span => { llmobs.annotate({ tags }) - assert.deepStrictEqual(LLMObsTagger.tagMap.get(span._span), { + assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), { '_ml_obs.meta.span.kind': 'llm', '_ml_obs.meta.ml_app': 'mlApp', '_ml_obs.llmobs_parent_id': 'undefined', From 8fd6a7df639e41fa0818788befa57d8d530f9774 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 14:22:24 -0400 Subject: [PATCH 39/48] remove duplicate constant --- packages/dd-trace/src/constants.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dd-trace/src/constants.js b/packages/dd-trace/src/constants.js index 06de85e2105..903da8c659b 100644 --- a/packages/dd-trace/src/constants.js +++ b/packages/dd-trace/src/constants.js @@ -32,7 +32,6 @@ module.exports = { ERROR_STACK: 'error.stack', IGNORE_OTEL_ERROR: Symbol('ignore.otel.error'), COMPONENT: 'component', - SVC_SRC_KEY: '_dd.svc_src', CLIENT_PORT_KEY: 'network.destination.port', PEER_SERVICE_KEY: 'peer.service', PEER_SERVICE_SOURCE_KEY: '_dd.peer.service.source', From ab8abc199a80a8403bf85f6b0b4f76b082f81438 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 15:31:15 -0400 Subject: [PATCH 40/48] remove use of scope in otel context manager --- packages/datadog-plugin-dd-trace-api/test/index.spec.js | 2 +- packages/dd-trace/src/aiguard/sdk.js | 3 +-- packages/dd-trace/src/llmobs/sdk.js | 8 ++++++-- packages/dd-trace/src/opentelemetry/context_manager.js | 8 +++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/datadog-plugin-dd-trace-api/test/index.spec.js b/packages/datadog-plugin-dd-trace-api/test/index.spec.js index 3f030b5b815..4b35cfb6f7b 100644 --- a/packages/datadog-plugin-dd-trace-api/test/index.spec.js +++ b/packages/datadog-plugin-dd-trace-api/test/index.spec.js @@ -144,7 +144,7 @@ describe('Plugin', () => { ret: dummySpan, }) span = tracer.startSpan.getCall(0).returnValue - span = span._span || span + span = span._span sinon.spy(span) }) diff --git a/packages/dd-trace/src/aiguard/sdk.js b/packages/dd-trace/src/aiguard/sdk.js index a7a27a1ade0..d1ac9734d42 100644 --- a/packages/dd-trace/src/aiguard/sdk.js +++ b/packages/dd-trace/src/aiguard/sdk.js @@ -144,7 +144,6 @@ class AIGuard extends NoopAIGuard { } const { block = true } = opts ?? {} return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => { - span = span._span const last = messages[messages.length - 1] const target = this.#isToolCall(last) ? 'tool' : 'prompt' span.setTag(AI_GUARD_TARGET_TAG_KEY, target) @@ -157,7 +156,7 @@ class AIGuard extends NoopAIGuard { const metaStruct = { messages: this.#buildMessagesForMetaStruct(messages), } - span.meta_struct = { + span._span.meta_struct = { [AI_GUARD_META_STRUCT_KEY]: metaStruct, } const rootSpan = span.context()?._trace?.started?.[0] diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index c90a524a276..a7c7ca558d7 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -208,7 +208,7 @@ class LLMObs extends NoopLLMObs { return this._tracer.wrap(name, spanOptions, wrapped) } - annotate (span, options) { + annotate (span, options, autoinstrumented = false) { if (!this.enabled) return if (!span) { @@ -248,11 +248,15 @@ class LLMObs extends NoopLLMObs { } throw e } finally { - telemetry.recordLLMObsAnnotate(span, err) + if (autoinstrumented === false) { + telemetry.recordLLMObsAnnotate(span, err) + } } } _annotate (span, options) { + if (!this.enabled) return + const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND] if (!spanKind) { const err = new Error('LLMObs span must have a span kind specified') diff --git a/packages/dd-trace/src/opentelemetry/context_manager.js b/packages/dd-trace/src/opentelemetry/context_manager.js index 93236078268..c4f9ea12dbd 100644 --- a/packages/dd-trace/src/opentelemetry/context_manager.js +++ b/packages/dd-trace/src/opentelemetry/context_manager.js @@ -5,7 +5,6 @@ const { storage } = require('../../../datadog-core') const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage') const tracer = require('../../') -const PublicSpan = require('../opentracing/public/span') const SpanContext = require('./span_context') class ContextManager { @@ -61,7 +60,6 @@ class ContextManager { // converts otel to dd with (context, fn, thisArg, ...args) { const span = trace.getSpan(context) - const ddScope = tracer.scope() const run = () => { const cb = thisArg == null ? fn : fn.bind(thisArg) return this._store.run(context, cb, ...args) @@ -75,7 +73,11 @@ class ContextManager { for (const baggage of baggageItems) { setBaggageItem(baggage[0], baggage[1].value) } - if (span && span._ddSpan) return ddScope.activate(new PublicSpan(span._ddSpan), run) + if (span && span._ddSpan) { + const ddSpan = span._ddSpan + const parentStore = storage('legacy').getStore(ddSpan._store) ?? storage('legacy').getStore() + return storage('legacy').run({ ...parentStore, span: ddSpan }, run) + } return run() } From dea3c693baf7b18e07f0b4a7cad947d728d0c9ce Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 15:56:13 -0400 Subject: [PATCH 41/48] removed PublicSpan from inject --- packages/dd-trace/src/noop/proxy.js | 7 +++++-- packages/dd-trace/src/opentracing/tracer.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/dd-trace/src/noop/proxy.js b/packages/dd-trace/src/noop/proxy.js index 4a687914b65..f12ad2cee79 100644 --- a/packages/dd-trace/src/noop/proxy.js +++ b/packages/dd-trace/src/noop/proxy.js @@ -100,8 +100,11 @@ class NoopProxy { return new PublicSpan(this._tracer.startSpan.apply(this._tracer, arguments)) } - inject () { - return this._tracer.inject.apply(this._tracer, arguments) + inject (context, format, carrier) { + if (context instanceof PublicSpan) { + context = context._span + } + return this._tracer.inject(context, format, carrier) } extract () { diff --git a/packages/dd-trace/src/opentracing/tracer.js b/packages/dd-trace/src/opentracing/tracer.js index 2dfa82febd5..3c4d5aa1183 100644 --- a/packages/dd-trace/src/opentracing/tracer.js +++ b/packages/dd-trace/src/opentracing/tracer.js @@ -82,7 +82,7 @@ class DatadogTracer { } inject (context, format, carrier) { - if (context instanceof Span || context instanceof PublicSpan) { + if (context instanceof Span) { context = context.context() } From 687ada73fcc7b8b2438b969b22f1010fe547a2d3 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 18:18:14 -0400 Subject: [PATCH 42/48] remove use of tracer.scope from getRootSpan --- packages/dd-trace/src/appsec/sdk/set_user.js | 2 +- .../dd-trace/src/appsec/sdk/track_event.js | 10 +++---- .../dd-trace/src/appsec/sdk/user_blocking.js | 4 +-- packages/dd-trace/src/appsec/sdk/utils.js | 8 +++-- packages/dd-trace/src/priority_sampler.js | 4 +-- .../dd-trace/test/appsec/sdk/set_user.spec.js | 2 +- .../test/appsec/sdk/user_blocking.spec.js | 6 ++-- .../dd-trace/test/appsec/sdk/utils.spec.js | 30 +++++++++---------- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/dd-trace/src/appsec/sdk/set_user.js b/packages/dd-trace/src/appsec/sdk/set_user.js index b053271b402..a25dafdc002 100644 --- a/packages/dd-trace/src/appsec/sdk/set_user.js +++ b/packages/dd-trace/src/appsec/sdk/set_user.js @@ -19,7 +19,7 @@ function setUser (tracer, user) { return } - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (!rootSpan) { log.warn('[ASM] Root span not available in setUser') return diff --git a/packages/dd-trace/src/appsec/sdk/track_event.js b/packages/dd-trace/src/appsec/sdk/track_event.js index a7556483c90..10388a51ae7 100644 --- a/packages/dd-trace/src/appsec/sdk/track_event.js +++ b/packages/dd-trace/src/appsec/sdk/track_event.js @@ -21,7 +21,7 @@ function trackUserLoginSuccessEvent (tracer, user, metadata) { incrementSdkEventMetric('login_success', 'v1') - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (!rootSpan) { log.warn('[ASM] Root span not available in trackUserLoginSuccessEvent') return @@ -54,7 +54,7 @@ function trackUserLoginFailureEvent (tracer, userId, exists, metadata) { ...metadata, } - trackEvent('users.login.failure', fields, 'trackUserLoginFailureEvent', getRootSpan(tracer)) + trackEvent('users.login.failure', fields, 'trackUserLoginFailureEvent', getRootSpan()) runWaf('users.login.failure', { login: userId }) @@ -67,7 +67,7 @@ function trackCustomEvent (tracer, eventName, metadata) { return } - trackEvent(eventName, metadata, 'trackCustomEvent', getRootSpan(tracer)) + trackEvent(eventName, metadata, 'trackCustomEvent', getRootSpan()) incrementSdkEventMetric('custom', 'v1') @@ -84,7 +84,7 @@ function trackUserLoginSuccessV2 (tracer, login, user, metadata) { incrementSdkEventMetric('login_success', 'v2') - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (!rootSpan) { log.warn('[ASM] Root span not available in eventTrackingV2.trackUserLoginSuccess') return @@ -122,7 +122,7 @@ function trackUserLoginFailureV2 (tracer, login, exists, metadata) { incrementSdkEventMetric('login_failure', 'v2') - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (!rootSpan) { log.warn('[ASM] Root span not available in eventTrackingV2.trackUserLoginFailure') return diff --git a/packages/dd-trace/src/appsec/sdk/user_blocking.js b/packages/dd-trace/src/appsec/sdk/user_blocking.js index 91f8cb7504c..7fbcdd529ac 100644 --- a/packages/dd-trace/src/appsec/sdk/user_blocking.js +++ b/packages/dd-trace/src/appsec/sdk/user_blocking.js @@ -19,7 +19,7 @@ function checkUserAndSetUser (tracer, user) { return false } - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (rootSpan) { if (!rootSpan.context()._tags['usr.id']) { setUserTags(user, rootSpan) @@ -45,7 +45,7 @@ function blockRequest (tracer, req, res) { return false } - const rootSpan = getRootSpan(tracer) + const rootSpan = getRootSpan() if (!rootSpan) { log.warn('[ASM] Root span not available in blockRequest') return false diff --git a/packages/dd-trace/src/appsec/sdk/utils.js b/packages/dd-trace/src/appsec/sdk/utils.js index 546938382c7..cb267ecc67f 100644 --- a/packages/dd-trace/src/appsec/sdk/utils.js +++ b/packages/dd-trace/src/appsec/sdk/utils.js @@ -1,7 +1,9 @@ 'use strict' -function getRootSpan (tracer) { - let span = tracer.scope().active() +const { storage } = require('../../../../datadog-core') + +function getRootSpan () { + let span = storage('legacy').getStore()?.span if (!span) return const context = span.context() @@ -26,4 +28,4 @@ function getRootSpan (tracer) { module.exports = { getRootSpan, -} +} \ No newline at end of file diff --git a/packages/dd-trace/src/priority_sampler.js b/packages/dd-trace/src/priority_sampler.js index 7ff135a16b1..cb4daf73358 100644 --- a/packages/dd-trace/src/priority_sampler.js +++ b/packages/dd-trace/src/priority_sampler.js @@ -388,9 +388,7 @@ class PrioritySampler { * @param {Product} [product] */ static keepTrace (span, product) { - // Unwrap PublicSpan (scope().active()) so _prioritySampler is reachable. - const target = span?._span ?? span - target?._prioritySampler?.setPriority(target, USER_KEEP, product) + span?._prioritySampler?.setPriority(span, USER_KEEP, product) } } diff --git a/packages/dd-trace/test/appsec/sdk/set_user.spec.js b/packages/dd-trace/test/appsec/sdk/set_user.spec.js index 5e8d7956b8f..94cecd0d86c 100644 --- a/packages/dd-trace/test/appsec/sdk/set_user.spec.js +++ b/packages/dd-trace/test/appsec/sdk/set_user.spec.js @@ -63,7 +63,7 @@ describe('set_user', () => { getRootSpan.returns(undefined) setUser(tracer, { id: 'user' }) - sinon.assert.calledOnceWithExactly(getRootSpan, tracer) + sinon.assert.calledOnce(getRootSpan) sinon.assert.calledOnceWithExactly(log.warn, '[ASM] Root span not available in setUser') sinon.assert.notCalled(rootSpan.setTag) sinon.assert.notCalled(waf.run) diff --git a/packages/dd-trace/test/appsec/sdk/user_blocking.spec.js b/packages/dd-trace/test/appsec/sdk/user_blocking.spec.js index 053a61ec098..4268744d82b 100644 --- a/packages/dd-trace/test/appsec/sdk/user_blocking.spec.js +++ b/packages/dd-trace/test/appsec/sdk/user_blocking.spec.js @@ -74,7 +74,7 @@ describe('user_blocking - Internal API', () => { it('should set user when not already set', () => { const ret = userBlocking.checkUserAndSetUser(tracer, { id: 'user' }) assert.strictEqual(ret, true) - sinon.assert.calledOnceWithExactly(getRootSpan, tracer) + sinon.assert.calledOnce(getRootSpan) sinon.assert.calledWithExactly(rootSpan.setTag, 'usr.id', 'user') sinon.assert.calledWithExactly(rootSpan.setTag, '_dd.appsec.user.collection_mode', 'sdk') }) @@ -86,7 +86,7 @@ describe('user_blocking - Internal API', () => { const ret = userBlocking.checkUserAndSetUser(tracer, { id: 'user' }) assert.strictEqual(ret, true) - sinon.assert.calledOnceWithExactly(getRootSpan, tracer) + sinon.assert.calledOnce(getRootSpan) sinon.assert.notCalled(rootSpan.setTag) }) @@ -95,7 +95,7 @@ describe('user_blocking - Internal API', () => { const ret = userBlocking.checkUserAndSetUser(tracer, { id: 'user' }) assert.strictEqual(ret, true) - sinon.assert.calledOnceWithExactly(getRootSpan, tracer) + sinon.assert.calledOnce(getRootSpan) sinon.assert.calledOnceWithExactly(log.warn, '[ASM] Root span not available in isUserBlocked') sinon.assert.notCalled(rootSpan.setTag) }) diff --git a/packages/dd-trace/test/appsec/sdk/utils.spec.js b/packages/dd-trace/test/appsec/sdk/utils.spec.js index 752c0d9acdb..f0ee90e70bb 100644 --- a/packages/dd-trace/test/appsec/sdk/utils.spec.js +++ b/packages/dd-trace/test/appsec/sdk/utils.spec.js @@ -18,9 +18,9 @@ describe('Appsec SDK utils', () => { describe('getRootSpan', () => { it('should return root span if there are no childs', () => { tracer.trace('parent', { }, parent => { - const root = getRootSpan(tracer) + const root = getRootSpan() - assert.strictEqual(root, parent) + assert.strictEqual(root, parent._span) }) }) @@ -28,7 +28,7 @@ describe('Appsec SDK utils', () => { const childOf = tracer.startSpan('parent') tracer.trace('child1', { childOf }, child1 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -39,7 +39,7 @@ describe('Appsec SDK utils', () => { childOf.context()._parentId = id() tracer.trace('child1', { childOf }, child1 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -52,7 +52,7 @@ describe('Appsec SDK utils', () => { tracer.trace('child1.1.2', { childOf: child11 }, child112 => {}) }) tracer.trace('child1.2', { childOf }, child12 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -63,9 +63,9 @@ describe('Appsec SDK utils', () => { childOf.setTag('_inferred_span', {}) tracer.trace('child1', { childOf }, child1 => { - const root = getRootSpan(tracer) + const root = getRootSpan() - assert.strictEqual(root, child1) + assert.strictEqual(root, child1._span) }) }) @@ -75,7 +75,7 @@ describe('Appsec SDK utils', () => { tracer.trace('child1', { childOf }, child1 => { child1.setTag('_inferred_span', {}) - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -88,9 +88,9 @@ describe('Appsec SDK utils', () => { tracer.trace('child1', { childOf }, child1 => { child1.setTag('_inferred_span', {}) - const root = getRootSpan(tracer) + const root = getRootSpan() - assert.strictEqual(root, child1) + assert.strictEqual(root, child1._span) }) }) @@ -101,7 +101,7 @@ describe('Appsec SDK utils', () => { tracer.trace('child1.1', { childOf }, child11 => {}) tracer.trace('child1.2', { childOf }, child12 => { tracer.trace('child1.2.1', { childOf: child12 }, child121 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, child12._span) }) }) @@ -115,7 +115,7 @@ describe('Appsec SDK utils', () => { child12.setTag('_inferred_span', {}) tracer.trace('child1.2.1', { childOf: child12 }, child121 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -133,7 +133,7 @@ describe('Appsec SDK utils', () => { child121.setTag('_inferred_span', {}) tracer.trace('child1.2.1.1', { childOf: child121 }, child1211 => { - const root = getRootSpan(tracer) + const root = getRootSpan() assert.strictEqual(root, childOf) }) @@ -153,9 +153,9 @@ describe('Appsec SDK utils', () => { child121.setTag('_inferred_span', {}) tracer.trace('child1.2.1.1', { childOf: child121 }, child1211 => { - const root = getRootSpan(tracer) + const root = getRootSpan() - assert.strictEqual(root, child1211) + assert.strictEqual(root, child1211._span) }) }) }) From 3d05617aa999a836d818f406b99fc768cb1e239f Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Mon, 13 Apr 2026 18:30:31 -0400 Subject: [PATCH 43/48] fix lint --- packages/dd-trace/src/appsec/sdk/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/src/appsec/sdk/utils.js b/packages/dd-trace/src/appsec/sdk/utils.js index cb267ecc67f..6fcf2402410 100644 --- a/packages/dd-trace/src/appsec/sdk/utils.js +++ b/packages/dd-trace/src/appsec/sdk/utils.js @@ -28,4 +28,4 @@ function getRootSpan () { module.exports = { getRootSpan, -} \ No newline at end of file +} From f4d7ed5c9b2f98c23613041692ae5bef3577ff16 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 14 Apr 2026 10:00:30 -0400 Subject: [PATCH 44/48] remove tracer.scope from context manager --- packages/dd-trace/src/opentelemetry/context_manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/dd-trace/src/opentelemetry/context_manager.js b/packages/dd-trace/src/opentelemetry/context_manager.js index c4f9ea12dbd..e583f62acc5 100644 --- a/packages/dd-trace/src/opentelemetry/context_manager.js +++ b/packages/dd-trace/src/opentelemetry/context_manager.js @@ -4,7 +4,6 @@ const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api') const { storage } = require('../../../datadog-core') const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage') -const tracer = require('../../') const SpanContext = require('./span_context') class ContextManager { @@ -16,7 +15,7 @@ class ContextManager { active () { const store = this._store.getStore() const baseContext = store || ROOT_CONTEXT - const activeSpan = tracer.scope().active()?._span + const activeSpan = storage('legacy').getStore()?.span const storedSpan = store ? trace.getSpan(store) : null From 24c851083ed4ba53635dfde31ce1e3d6ee2514d9 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 14 Apr 2026 10:25:14 -0400 Subject: [PATCH 45/48] remove trace-api changes --- packages/datadog-plugin-dd-trace-api/test/index.spec.js | 4 ---- packages/dd-trace/src/opentracing/public/span.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/datadog-plugin-dd-trace-api/test/index.spec.js b/packages/datadog-plugin-dd-trace-api/test/index.spec.js index 4b35cfb6f7b..7cda39c13de 100644 --- a/packages/datadog-plugin-dd-trace-api/test/index.spec.js +++ b/packages/datadog-plugin-dd-trace-api/test/index.spec.js @@ -144,7 +144,6 @@ describe('Plugin', () => { ret: dummySpan, }) span = tracer.startSpan.getCall(0).returnValue - span = span._span sinon.spy(span) }) @@ -208,7 +207,6 @@ describe('Plugin', () => { fn: span.setTag, self: dummySpan, ret: dummySpan, - args: ['key', 'value'], }) }) }) @@ -220,7 +218,6 @@ describe('Plugin', () => { fn: span.addTags, self: dummySpan, ret: dummySpan, - args: [{ key: 'value' }], }) }) }) @@ -231,7 +228,6 @@ describe('Plugin', () => { name: 'span:finish', fn: span.finish, self: dummySpan, - args: [123], }) }) }) diff --git a/packages/dd-trace/src/opentracing/public/span.js b/packages/dd-trace/src/opentracing/public/span.js index 3f9ef98dc69..a59680e8598 100644 --- a/packages/dd-trace/src/opentracing/public/span.js +++ b/packages/dd-trace/src/opentracing/public/span.js @@ -39,7 +39,7 @@ class PublicSpan { } addTags (tags) { - if (tags[SERVICE_KEY] || tags[SERVICE_NAME_KEY]) { + if (tags && (tags[SERVICE_KEY] || tags[SERVICE_NAME_KEY])) { this._span.setTag(SVC_SRC_KEY, 'm') } this._span.addTags(tags) From 8875d75c6b66baeb8ef8a1e15e6de011dbfc9de1 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 14 Apr 2026 14:55:23 -0400 Subject: [PATCH 46/48] changed some tests --- packages/datadog-plugin-connect/test/index.spec.js | 3 ++- packages/datadog-plugin-express/test/index.spec.js | 3 ++- packages/datadog-plugin-redis/test/client.spec.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/datadog-plugin-connect/test/index.spec.js b/packages/datadog-plugin-connect/test/index.spec.js index 43b881d0f89..178450bd608 100644 --- a/packages/datadog-plugin-connect/test/index.spec.js +++ b/packages/datadog-plugin-connect/test/index.spec.js @@ -9,6 +9,7 @@ const { after, afterEach, before, beforeEach, describe, it } = require('mocha') const sinon = require('sinon') const { assertObjectContains } = require('../../../integration-tests/helpers') +const { storage } = require('../../datadog-core') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const { withVersions } = require('../../dd-trace/test/setup/mocha') @@ -172,7 +173,7 @@ describe('Plugin', () => { let span app.use((req, res, next) => { - span = tracer.scope().active()._span + span = storage('legacy').getStore()?.span sinon.spy(span, 'finish') diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 4b70daaa42e..41d21ce0d8e 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -10,6 +10,7 @@ const sinon = require('sinon') const { assertObjectContains } = require('../../../integration-tests/helpers') const { NODE_MAJOR } = require('../../../version') +const { storage } = require('../../datadog-core') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const { withVersions } = require('../../dd-trace/test/setup/mocha') @@ -628,7 +629,7 @@ describe('Plugin', () => { let span app.use((req, res, next) => { - span = tracer.scope().active()._span + span = storage('legacy').getStore()?.span sinon.spy(span, 'finish') diff --git a/packages/datadog-plugin-redis/test/client.spec.js b/packages/datadog-plugin-redis/test/client.spec.js index 64562d8bbae..21c554a115e 100644 --- a/packages/datadog-plugin-redis/test/client.spec.js +++ b/packages/datadog-plugin-redis/test/client.spec.js @@ -139,7 +139,7 @@ describe('Plugin', () => { const span = tracer.startSpan('test') tracer.scope().activate(span, () => { client.get('foo', () => { - assert.strictEqual(tracer.scope().active(), span) + assert.strictEqual(span.context().active(), span) }) }) }) From 3a46cc70273dbe110d480c8eacb33e2b58e53255 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Tue, 14 Apr 2026 14:55:40 -0400 Subject: [PATCH 47/48] modified koa test --- .../datadog-plugin-apollo/test/index.spec.js | 7 +++++++ .../datadog-plugin-aws-sdk/test/aws-sdk.spec.js | 2 ++ .../test/index.spec.js | 16 ++++++++-------- .../test/index.spec.js | 2 ++ packages/datadog-plugin-fetch/test/index.spec.js | 2 ++ .../datadog-plugin-graphql/test/index.spec.js | 4 ++++ packages/datadog-plugin-http/test/client.spec.js | 2 ++ packages/datadog-plugin-koa/test/index.spec.js | 5 +++-- .../datadog-plugin-redis/test/client.spec.js | 7 ++++--- .../datadog-plugin-sharedb/test/index.spec.js | 3 +++ .../datadog-plugin-undici/test/index.spec.js | 2 ++ packages/dd-trace/src/plugins/tracing.js | 9 ++++++++- .../dd-trace/test/opentelemetry/tracer.spec.js | 3 ++- .../dd-trace/test/plugins/log_plugin.spec.js | 8 ++++---- packages/dd-trace/test/tracer.spec.js | 12 ++++++------ 15 files changed, 59 insertions(+), 25 deletions(-) diff --git a/packages/datadog-plugin-apollo/test/index.spec.js b/packages/datadog-plugin-apollo/test/index.spec.js index a72a0b291e7..e9cd809f02c 100644 --- a/packages/datadog-plugin-apollo/test/index.spec.js +++ b/packages/datadog-plugin-apollo/test/index.spec.js @@ -7,6 +7,7 @@ const sinon = require('sinon') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants.js') const agent = require('../../dd-trace/test/plugins/agent.js') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withNamingSchema, withVersions } = require('../../dd-trace/test/setup/mocha') const { assertObjectContains } = require('../../../integration-tests/helpers') const accounts = require('./fixtures.js') @@ -592,6 +593,11 @@ describe('Plugin', () => { const executeSpan = config.hooks.execute.firstCall.args[0] const postprocessingSpan = config.hooks.postprocessing.firstCall.args[0] + assert.ok(requestSpan instanceof PublicSpan) + assert.ok(planSpan instanceof PublicSpan) + assert.ok(executeSpan instanceof PublicSpan) + assert.ok(postprocessingSpan instanceof PublicSpan) + assert.strictEqual(requestSpan.context()._name, expectedSchema.server.opName) assert.strictEqual(planSpan.context()._name, 'apollo.gateway.plan') assert.strictEqual(executeSpan.context()._name, 'apollo.gateway.execute') @@ -627,6 +633,7 @@ describe('Plugin', () => { sinon.assert.notCalled(config.hooks.postprocessing) const validateSpan = config.hooks.validate.firstCall.args[0] + assert.ok(validateSpan instanceof PublicSpan) const validateCtx = config.hooks.validate.firstCall.args[1] assert.strictEqual(validateSpan.context()._name, 'apollo.gateway.validate') diff --git a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js index b9bf5ecb86e..5a80935315b 100644 --- a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js @@ -7,6 +7,7 @@ const semver = require('semver') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withVersions } = require('../../dd-trace/test/setup/mocha') const { assertObjectContains } = require('../../../integration-tests/helpers') const { setup, sort } = require('./spec_helpers') @@ -258,6 +259,7 @@ describe('Plugin', () => { service: 'test', hooks: { request (span, response) { + assert.ok(span instanceof PublicSpan) span.setTag('hook.operation', response.request.operation) span.addTags({ error: 0, diff --git a/packages/datadog-plugin-child_process/test/index.spec.js b/packages/datadog-plugin-child_process/test/index.spec.js index 4ec5d60eeee..67f09fe86ef 100644 --- a/packages/datadog-plugin-child_process/test/index.spec.js +++ b/packages/datadog-plugin-child_process/test/index.spec.js @@ -517,9 +517,9 @@ describe('Child process plugin', () => { beforeEach((done) => { if (hasParentSpan) { - parentSpan = tracer.startSpan('parent') + parentSpan = tracer._tracer.startSpan('parent') parentSpan.finish() - tracer.scope().activate(parentSpan, done) + storage('legacy').run({ span: parentSpan }, done) } else { storage('legacy').enterWith({}) done() @@ -551,10 +551,10 @@ describe('Child process plugin', () => { it('should maintain previous span after the execution', (done) => { const res = childProcess[methodName]('ls') const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan?._span) + assert.strictEqual(span, parentSpan) if (async) { res.on('close', () => { - assert.strictEqual(span, parentSpan?._span) + assert.strictEqual(span, parentSpan) done() }) } else { @@ -566,7 +566,7 @@ describe('Child process plugin', () => { it('should maintain previous span in the callback', (done) => { childProcess[methodName]('ls', () => { const span = storage('legacy').getStore()?.span - assert.strictEqual(span, parentSpan?._span) + assert.strictEqual(span, parentSpan) done() }) }) @@ -659,9 +659,9 @@ describe('Child process plugin', () => { ] if (parentSpan) { beforeEach((done) => { - const parentSpan = tracer.startSpan('parent') - parentSpan.finish() - tracer.scope().activate(parentSpan, done) + const span = tracer._tracer.startSpan('parent') + span.finish() + storage('legacy').run({ span }, done) }) } diff --git a/packages/datadog-plugin-elasticsearch/test/index.spec.js b/packages/datadog-plugin-elasticsearch/test/index.spec.js index 93904155d45..e11b632c1ef 100644 --- a/packages/datadog-plugin-elasticsearch/test/index.spec.js +++ b/packages/datadog-plugin-elasticsearch/test/index.spec.js @@ -6,6 +6,7 @@ const { after, afterEach, before, beforeEach, describe, it } = require('mocha') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { breakThen, unbreakThen } = require('../../dd-trace/test/plugins/helpers') const { withNamingSchema, withPeerService, withVersions } = require('../../dd-trace/test/setup/mocha') const { assertObjectContains } = require('../../../integration-tests/helpers') @@ -346,6 +347,7 @@ describe('Plugin', () => { service: 'custom', hooks: { query: (span, params) => { + assert.ok(span instanceof PublicSpan) span.addTags({ 'elasticsearch.params': 'foo', 'elasticsearch.method': params.method }) }, }, diff --git a/packages/datadog-plugin-fetch/test/index.spec.js b/packages/datadog-plugin-fetch/test/index.spec.js index 51608c112b6..fd6dfcb544b 100644 --- a/packages/datadog-plugin-fetch/test/index.spec.js +++ b/packages/datadog-plugin-fetch/test/index.spec.js @@ -8,6 +8,7 @@ const tags = require('../../../ext/tags') const { storage } = require('../../datadog-core') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withNamingSchema } = require('../../dd-trace/test/setup/mocha') const { assertObjectContains } = require('../../../integration-tests/helpers') const { rawExpectedSchema } = require('./naming') @@ -516,6 +517,7 @@ describe('Plugin', function () { config = { hooks: { request: (span, req, res) => { + assert.ok(span instanceof PublicSpan) span.setTag('foo', '/foo') }, }, diff --git a/packages/datadog-plugin-graphql/test/index.spec.js b/packages/datadog-plugin-graphql/test/index.spec.js index 9d943372a45..c75f91460c8 100644 --- a/packages/datadog-plugin-graphql/test/index.spec.js +++ b/packages/datadog-plugin-graphql/test/index.spec.js @@ -13,6 +13,7 @@ const sinon = require('sinon') const { assertObjectContains } = require('../../../integration-tests/helpers') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withNamingSchema, withVersions } = require('../../dd-trace/test/setup/mocha') const plugin = require('../src') const { expectedSchema, rawExpectedSchema } = require('./naming') @@ -1819,6 +1820,7 @@ describe('Plugin', () => { sinon.assert.calledOnce(config.hooks.execute) const span = config.hooks.execute.firstCall.args[0] + assert.ok(span instanceof PublicSpan) const args = config.hooks.execute.firstCall.args[1] const res = config.hooks.execute.firstCall.args[2] @@ -1860,6 +1862,7 @@ describe('Plugin', () => { sinon.assert.calledOnce(config.hooks.validate) const span = config.hooks.validate.firstCall.args[0] + assert.ok(span instanceof PublicSpan) const hookDocument = config.hooks.validate.firstCall.args[1] const hookErrors = config.hooks.validate.firstCall.args[2] @@ -1885,6 +1888,7 @@ describe('Plugin', () => { sinon.assert.calledOnce(config.hooks.parse) const span = config.hooks.parse.firstCall.args[0] + assert.ok(span instanceof PublicSpan) const hookSource = config.hooks.parse.firstCall.args[1] const hookDocument = config.hooks.parse.firstCall.args[2] diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index 628d6ed5297..44c859f659d 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -8,6 +8,7 @@ const { satisfies } = require('semver') const tags = require('../../../ext/tags') const { storage } = require('../../datadog-core') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withNamingSchema, withPeerService } = require('../../dd-trace/test/setup/mocha') const key = fs.readFileSync(path.join(__dirname, './ssl/test.key')) const cert = fs.readFileSync(path.join(__dirname, './ssl/test.crt')) @@ -1291,6 +1292,7 @@ describe('Plugin', () => { client: { hooks: { request: (span, req, res) => { + assert.ok(span instanceof PublicSpan) span.setTag('resource.name', `${req.method} ${req._route}`) }, }, diff --git a/packages/datadog-plugin-koa/test/index.spec.js b/packages/datadog-plugin-koa/test/index.spec.js index 4952a44fb24..e76155a557d 100644 --- a/packages/datadog-plugin-koa/test/index.spec.js +++ b/packages/datadog-plugin-koa/test/index.spec.js @@ -10,6 +10,7 @@ const semver = require('semver') const sinon = require('sinon') const { assertObjectContains } = require('../../../integration-tests/helpers') +const { storage } = require('../../datadog-core') const { ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const { withVersions } = require('../../dd-trace/test/setup/mocha') @@ -192,7 +193,7 @@ describe('Plugin', () => { let childSpan app.use((ctx, next) => { - parentSpan = tracer.scope().active()._span + parentSpan = storage('legacy').getStore()?.span sinon.spy(parentSpan, 'finish') @@ -213,7 +214,7 @@ describe('Plugin', () => { }) app.use((ctx, next) => { - childSpan = tracer.scope().active()._span + childSpan = storage('legacy').getStore()?.span sinon.spy(childSpan, 'finish') diff --git a/packages/datadog-plugin-redis/test/client.spec.js b/packages/datadog-plugin-redis/test/client.spec.js index 21c554a115e..eecd1a6b090 100644 --- a/packages/datadog-plugin-redis/test/client.spec.js +++ b/packages/datadog-plugin-redis/test/client.spec.js @@ -4,6 +4,7 @@ const assert = require('node:assert') const { after, afterEach, before, beforeEach, describe, it } = require('mocha') +const { storage } = require('../../datadog-core') const { ERROR_MESSAGE, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const { breakThen, unbreakThen } = require('../../dd-trace/test/plugins/helpers') @@ -136,10 +137,10 @@ describe('Plugin', () => { ) it('should restore the parent context in the callback', async () => { - const span = tracer.startSpan('test') - tracer.scope().activate(span, () => { + const span = tracer._tracer.startSpan('test') + storage('legacy').run({ span }, () => { client.get('foo', () => { - assert.strictEqual(span.context().active(), span) + assert.strictEqual(storage('legacy').getStore()?.span, span) }) }) }) diff --git a/packages/datadog-plugin-sharedb/test/index.spec.js b/packages/datadog-plugin-sharedb/test/index.spec.js index 696b1d8f581..f04aa62c9a1 100644 --- a/packages/datadog-plugin-sharedb/test/index.spec.js +++ b/packages/datadog-plugin-sharedb/test/index.spec.js @@ -7,6 +7,7 @@ const sinon = require('sinon') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { let ShareDB @@ -204,6 +205,8 @@ describe('Plugin', () => { sinon.match.object, sinon.match.object ) + assert.ok(receiveHookSpy.firstCall.args[0] instanceof PublicSpan) + assert.ok(replyHookSpy.firstCall.args[0] instanceof PublicSpan) }) .then(done) .catch(done) diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 2bb66459143..86c6388455d 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -8,6 +8,7 @@ const tags = require('../../../ext/tags') const { NODE_MAJOR } = require('../../../version') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const PublicSpan = require('../../dd-trace/src/opentracing/public/span') const { withNamingSchema, withVersions } = require('../../dd-trace/test/setup/mocha') const { assertObjectContains } = require('../../../integration-tests/helpers') const { rawExpectedSchema } = require('./naming') @@ -567,6 +568,7 @@ describe('Plugin', () => { config = { hooks: { request: (span, req, res) => { + assert.ok(span instanceof PublicSpan) span.setTag('foo', '/foo') }, }, diff --git a/packages/dd-trace/src/plugins/tracing.js b/packages/dd-trace/src/plugins/tracing.js index b50fab92d32..ce74cd14b2b 100644 --- a/packages/dd-trace/src/plugins/tracing.js +++ b/packages/dd-trace/src/plugins/tracing.js @@ -3,6 +3,7 @@ const { storage } = require('../../../datadog-core') const analyticsSampler = require('../analytics_sampler') const { COMPONENT, SVC_SRC_KEY } = require('../constants') +const PublicSpan = require('../opentracing/public/span') const Plugin = require('./plugin') class TracingPlugin extends Plugin { @@ -60,11 +61,17 @@ class TracingPlugin extends Plugin { * @returns {object} */ configure (config) { + const hooks = config.hooks || {} + const wrappedHooks = {} + for (const [name, hook] of Object.entries(hooks)) { + wrappedHooks[name] = (span, ...args) => hook(new PublicSpan(span), ...args) + } + return super.configure({ ...config, hooks: { [this.operation]: () => {}, - ...config.hooks, + ...wrappedHooks, }, }) } diff --git a/packages/dd-trace/test/opentelemetry/tracer.spec.js b/packages/dd-trace/test/opentelemetry/tracer.spec.js index 10626fc5e31..c4bf426028a 100644 --- a/packages/dd-trace/test/opentelemetry/tracer.spec.js +++ b/packages/dd-trace/test/opentelemetry/tracer.spec.js @@ -8,6 +8,7 @@ const sinon = require('sinon') const api = require('@opentelemetry/api') const { hrTime, timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/core') +const { storage } = require('../../../datadog-core') require('../setup/core') require('../../').init() const TracerProvider = require('../../src/opentelemetry/tracer_provider') @@ -125,7 +126,7 @@ describe('OTel Tracer', () => { otelTracer.startActiveSpan('name', (span) => { assert.ok(span instanceof Span) - assert.strictEqual(span._ddSpan, tracer.scope().active()._span) + assert.strictEqual(span._ddSpan, storage('legacy').getStore()?.span) }) }) diff --git a/packages/dd-trace/test/plugins/log_plugin.spec.js b/packages/dd-trace/test/plugins/log_plugin.spec.js index 2d4cde4476a..9bff926d86c 100644 --- a/packages/dd-trace/test/plugins/log_plugin.spec.js +++ b/packages/dd-trace/test/plugins/log_plugin.spec.js @@ -4,12 +4,12 @@ const assert = require('node:assert/strict') const { describe, it } = require('mocha') const { channel } = require('dc-polyfill') +const { storage } = require('../../../datadog-core') const { assertObjectContains } = require('../../../../integration-tests/helpers') require('../setup/core') const LogPlugin = require('../../src/plugins/log_plugin') const Tracer = require('../../src/tracer') -const PublicSpan = require('../../src/opentracing/public/span') const getConfig = require('../../src/config') const testLogChannel = channel('apm:test:log') @@ -52,16 +52,16 @@ describe('LogPlugin', () => { }) it('should include trace_id and span_id when a span is active', () => { - const span = new PublicSpan(tracer.startSpan('test')) + const span = tracer.startSpan('test') - tracer.scope().activate(span, () => { + storage('legacy').run({ span }, () => { const data = { message: {} } testLogChannel.publish(data) const { message } = data assertObjectContains(message.dd, config) - // Should have trace/span data when none is active + // Should have trace/span data when a span is active assert.strictEqual(message.dd.trace_id, span.context().toTraceId(true)) assert.strictEqual(message.dd.span_id, span.context().toSpanId()) }) diff --git a/packages/dd-trace/test/tracer.spec.js b/packages/dd-trace/test/tracer.spec.js index 4627c021702..cdcf3376dad 100644 --- a/packages/dd-trace/test/tracer.spec.js +++ b/packages/dd-trace/test/tracer.spec.js @@ -7,9 +7,9 @@ const sinon = require('sinon') const { assertObjectContains } = require('../../../integration-tests/helpers') require('./setup/core') +const { storage } = require('../../datadog-core') const Tracer = require('../src/tracer') const Span = require('../src/opentracing/span') -const PublicSpan = require('../src/opentracing/public/span') const getConfig = require('../src/config') const tags = require('../../../ext/tags') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') @@ -109,9 +109,9 @@ describe('Tracer', () => { }) it('should start the span as a child of the active span', () => { - const childOf = new PublicSpan(tracer.startSpan('parent')) + const childOf = tracer.startSpan('parent') - tracer.scope().activate(childOf, () => { + storage('legacy').run({ span: childOf }, () => { tracer.trace('name', {}, span => { assert.strictEqual(span.context()._parentId.toString(10), childOf.context().toSpanId()) }) @@ -119,10 +119,10 @@ describe('Tracer', () => { }) it('should allow overriding the parent span', () => { - const root = new PublicSpan(tracer.startSpan('root')) - const childOf = new PublicSpan(tracer.startSpan('parent')) + const root = tracer.startSpan('root') + const childOf = tracer.startSpan('parent') - tracer.scope().activate(root, () => { + storage('legacy').run({ span: root }, () => { tracer.trace('name', { childOf }, span => { assert.strictEqual(span.context()._parentId.toString(10), childOf.context().toSpanId()) }) From 3b65eec4d96247f83eae3aacfc3d96c73dfd9be4 Mon Sep 17 00:00:00 2001 From: pabloerhard Date: Wed, 15 Apr 2026 14:45:06 -0400 Subject: [PATCH 48/48] fix duplicate after conflict --- packages/dd-trace/test/appsec/sdk/utils.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dd-trace/test/appsec/sdk/utils.spec.js b/packages/dd-trace/test/appsec/sdk/utils.spec.js index 4bd385f64d5..61cf67dc54a 100644 --- a/packages/dd-trace/test/appsec/sdk/utils.spec.js +++ b/packages/dd-trace/test/appsec/sdk/utils.spec.js @@ -19,7 +19,6 @@ describe('Appsec SDK utils', () => { it('should return root span if there are no childs', () => { tracer.trace('parent', {}, parent => { const root = getRootSpan() - const root = getRootSpan() assert.strictEqual(root, parent._span) })