Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
753e471
internal API split for Span and Scope
pabloerhard Mar 28, 2026
29c0c6b
removed the need of unwrapping span
pabloerhard Mar 29, 2026
e608c73
fixed proxy tests
pabloerhard Mar 29, 2026
e486dfe
make tests comply with public api
pabloerhard Mar 29, 2026
80a0c74
first batch of test fixes
pabloerhard Mar 29, 2026
60973e0
more test fixes
pabloerhard Mar 29, 2026
db7026a
fix koa
pabloerhard Mar 29, 2026
075344b
fixed proxy and some failing tests
pabloerhard Mar 30, 2026
8bf7c64
fixed aiguard
pabloerhard Mar 30, 2026
7d394da
removed PublicSpan from wrap
pabloerhard Mar 30, 2026
7aa71b0
fix cypress
pabloerhard Mar 30, 2026
65178a2
fix context manager
pabloerhard Mar 30, 2026
baa045c
fix tracer test
pabloerhard Mar 30, 2026
790cbe5
removed scope and trace wrapping
pabloerhard Mar 30, 2026
ccd34ec
wrap tracer
pabloerhard Mar 30, 2026
42345a5
Merge branch 'master' into pabloerhard/public-span-wrapper
pabloerhard Mar 30, 2026
95fff2a
set back wrap and fixed tests
pabloerhard Mar 30, 2026
b088cb8
fix llmobs sdk
pabloerhard Mar 30, 2026
fe92453
fix llmobs and appsec
pabloerhard Mar 30, 2026
6e52e67
read methods from DatadogSpan
pabloerhard Mar 30, 2026
5eba124
retore dd-trace-api tests
pabloerhard Mar 30, 2026
8267e3a
removed spies
pabloerhard Mar 30, 2026
c290b61
moved wrap into constructor
pabloerhard Mar 30, 2026
8a6477a
simplified code and removed some changes
pabloerhard Mar 31, 2026
3a3991a
simplified small changes
pabloerhard Apr 7, 2026
2023f86
Merge branch 'master' into pabloerhard/public-span-wrapper
pabloerhard Apr 7, 2026
dfc4688
removed space
pabloerhard Apr 7, 2026
187727b
removed llmobs logic and used storage instead
pabloerhard Apr 9, 2026
742d492
Merge branch 'master' into pabloerhard/public-span-wrapper
pabloerhard Apr 9, 2026
1d75fbd
remove scope file
pabloerhard Apr 9, 2026
0ecd858
unwrap span on internal methods
pabloerhard Apr 10, 2026
d8be3e0
fix linter
pabloerhard Apr 10, 2026
5b097d9
add TODO
pabloerhard Apr 10, 2026
c9c1463
removed span fallback
pabloerhard Apr 10, 2026
941e32f
added back fallback
pabloerhard Apr 10, 2026
4583583
added internal annotate
pabloerhard Apr 10, 2026
149f328
retrying fallback with fix
pabloerhard Apr 10, 2026
8f05472
remove use of internal span on tests
pabloerhard Apr 13, 2026
efbae5d
fix typo
pabloerhard Apr 13, 2026
1d2dad3
removed or in otel context manager
pabloerhard Apr 13, 2026
45ec521
simplified tagMap.get test changes
pabloerhard Apr 13, 2026
967dcc1
Merge branch 'master' into pabloerhard/public-span-wrapper
pabloerhard Apr 13, 2026
8fd6a7d
remove duplicate constant
pabloerhard Apr 13, 2026
ab8abc1
remove use of scope in otel context manager
pabloerhard Apr 13, 2026
dea3c69
removed PublicSpan from inject
pabloerhard Apr 13, 2026
687ada7
remove use of tracer.scope from getRootSpan
pabloerhard Apr 13, 2026
3d05617
fix lint
pabloerhard Apr 13, 2026
f4d7ed5
remove tracer.scope from context manager
pabloerhard Apr 14, 2026
24c8510
remove trace-api changes
pabloerhard Apr 14, 2026
8875d75
changed some tests
pabloerhard Apr 14, 2026
3a46cc7
modified koa test
pabloerhard Apr 14, 2026
17d36d3
Merge branch 'master' into pabloerhard/public-span-wrapper
pabloerhard Apr 15, 2026
3b65eec
fix duplicate after conflict
pabloerhard Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions packages/datadog-plugin-child_process/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-plugin-child_process/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-plugin-connect/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
3 changes: 1 addition & 2 deletions packages/datadog-plugin-cypress/src/cypress-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1047,11 +1047,10 @@ class CypressPlugin {
if (isQuarantinedFromSupport) {
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
}

const finishedTest = {
testName,
testStatus,
finishTime: this.activeTestSpan._getTime(), // we store the finish time here
finishTime: this.activeTestSpan._span._getTime(), // we store the finish time here
testSpan: this.activeTestSpan,
isEfdRetry,
isAttemptToFix,
Expand Down
4 changes: 4 additions & 0 deletions packages/datadog-plugin-dd-trace-api/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ describe('Plugin', () => {
ret: dummySpan,
})
span = tracer.startSpan.getCall(0).returnValue
span = span._span || span
sinon.spy(span)
})

Expand Down Expand Up @@ -207,6 +208,7 @@ describe('Plugin', () => {
fn: span.setTag,
self: dummySpan,
ret: dummySpan,
args: ['key', 'value'],
})
})
})
Expand All @@ -218,6 +220,7 @@ describe('Plugin', () => {
fn: span.addTags,
self: dummySpan,
ret: dummySpan,
args: [{ key: 'value' }],
})
})
})
Expand All @@ -228,6 +231,7 @@ describe('Plugin', () => {
name: 'span:finish',
fn: span.finish,
self: dummySpan,
args: [123],
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-plugin-express/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
4 changes: 2 additions & 2 deletions packages/datadog-plugin-koa/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -213,7 +213,7 @@ describe('Plugin', () => {
})

app.use((ctx, next) => {
childSpan = tracer.scope().active()
childSpan = tracer.scope().active()._span

sinon.spy(childSpan, 'finish')

Expand Down
4 changes: 2 additions & 2 deletions packages/datadog-plugin-redis/test/client.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
assert.strictEqual(tracer.scope().active(), span)
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-plugin-rhea/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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!' })
Expand Down
1 change: 1 addition & 0 deletions packages/dd-trace/src/aiguard/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class AIGuard extends NoopAIGuard {
}
const { block = true } = opts ?? {}
return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (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)
Expand Down
1 change: 1 addition & 0 deletions packages/dd-trace/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Comment thread
pabloerhard marked this conversation as resolved.
Outdated
CLIENT_PORT_KEY: 'network.destination.port',
PEER_SERVICE_KEY: 'peer.service',
PEER_SERVICE_SOURCE_KEY: '_dd.peer.service.source',
Expand Down
26 changes: 23 additions & 3 deletions packages/dd-trace/src/llmobs/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ 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 {
getFunctionArguments,
Expand Down Expand Up @@ -209,7 +229,7 @@ class LLMObs extends NoopLLMObs {
span = this._active()
}

if ((span && !options) && !(span instanceof Span)) {
if ((span && !options) && !isSpan(span)) {
options = span
span = this._active()
}
Expand All @@ -230,7 +250,7 @@ class LLMObs extends NoopLLMObs {
err = 'invalid_span_type'
throw new Error('Span must be an LLMObs-generated span')
}
if (span._duration !== undefined) {
if (unwrapSpan(span)._duration !== undefined) {
err = 'invalid_finished_span'
throw new Error('Cannot annotate a finished span')
}
Expand Down Expand Up @@ -287,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)) {
if (!isSpan(span)) {
err = 'invalid_span'
throw new TypeError('Span must be a valid Span object.')
}
Expand Down
36 changes: 26 additions & 10 deletions packages/dd-trace/src/llmobs/span_processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
ERROR_TYPE,
ERROR_STACK,
} = require('../constants')
const PublicSpan = require('../opentracing/public/span')
const {
SPAN_KIND,
MODEL_NAME,
Expand All @@ -35,6 +36,17 @@ 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 () {
this.input = []
Expand Down Expand Up @@ -73,15 +85,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],
Expand All @@ -99,12 +115,12 @@ class LLMObsSpanProcessor {
}
}

format (span) {
format (span, tagMapKey) {
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]

Expand Down Expand Up @@ -156,7 +172,7 @@ class LLMObsSpanProcessor {

const name = mlObsTags[NAME] || span._name

const tags = this.#getTags(span, mlApp, sessionId, error)
const tags = this.#getTags(span, tagMapKey, mlApp, sessionId, error)
llmObsSpan._tags = tags

const processedSpan = this.#runProcessor(llmObsSpan)
Expand Down Expand Up @@ -246,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,
Expand All @@ -264,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
Expand Down
25 changes: 22 additions & 3 deletions packages/dd-trace/src/noop/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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')
Comment thread
pabloerhard marked this conversation as resolved.
const { SVC_SRC_KEY } = require('../../src/constants')
const NoopDogStatsDClient = require('./dogstatsd')
const NoopTracer = require('./tracer')

Expand Down Expand Up @@ -51,6 +53,12 @@ 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)
}
Expand All @@ -64,7 +72,12 @@ 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',
}
}
return this._tracer.wrap(name, options, fn)
}

Expand All @@ -73,8 +86,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 () {
Expand Down
3 changes: 2 additions & 1 deletion packages/dd-trace/src/opentelemetry/context_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does OTel need to know about the internal span? Not that it's necessarily a problem, just wondering. Also, in what cases would publicSpan?._span be undefined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed for this comparison: storedSpan._ddSpan === activeSpan


const storedSpan = store ? trace.getSpan(store) : null

Expand Down
24 changes: 24 additions & 0 deletions packages/dd-trace/src/opentracing/public/scope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict'

const PublicSpan = require('./span')

class Scope {
Comment thread
pabloerhard marked this conversation as resolved.
Outdated
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
Loading
Loading