dd-trace 6.9.0 → 6.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +42 -0
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -8
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +169 -17
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +5 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +550 -39
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-vitest/src/index.js +8 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
- package/packages/dd-trace/src/config/supported-configurations.json +10 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/manager.js +5 -1
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +1 -62
- package/packages/dd-trace/src/plugins/util/test.js +84 -5
- package/packages/dd-trace/src/plugins/util/web.js +53 -2
|
@@ -21,6 +21,38 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.currentStore)
|
|
24
|
+
|
|
25
|
+
// Explicit getConnection calls get an acquire span. Pool queries report their internal acquire
|
|
26
|
+
// on the query span instead, so each acquire is counted once.
|
|
27
|
+
this.addSub(`apm:${this.component}:pool:acquire:start`, ctx => {
|
|
28
|
+
const operation = `${this.component}.pool.acquire`
|
|
29
|
+
|
|
30
|
+
this.startSpan(operation, {
|
|
31
|
+
service: this.serviceName({ pluginConfig: this.config, dbConfig: ctx.conf, system: this.system }),
|
|
32
|
+
resource: operation,
|
|
33
|
+
startTime: ctx.startTime,
|
|
34
|
+
type: 'sql',
|
|
35
|
+
kind: 'client',
|
|
36
|
+
meta: {
|
|
37
|
+
'db.type': this.system,
|
|
38
|
+
'db.user': ctx.conf.user,
|
|
39
|
+
'db.name': ctx.conf.database,
|
|
40
|
+
'out.host': ctx.conf.host,
|
|
41
|
+
[CLIENT_PORT_KEY]: ctx.conf.port,
|
|
42
|
+
},
|
|
43
|
+
}, ctx)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
this.addSub(`apm:${this.component}:pool:acquire:finish`, ctx => {
|
|
47
|
+
const span = ctx.currentStore?.span
|
|
48
|
+
if (span === undefined) return
|
|
49
|
+
|
|
50
|
+
if (ctx.error) {
|
|
51
|
+
this.addError(ctx.error, span)
|
|
52
|
+
}
|
|
53
|
+
span.setTag(`${this.component}.pool.wait_time`, ctx.poolWaitTime)
|
|
54
|
+
this.finish(ctx)
|
|
55
|
+
})
|
|
24
56
|
}
|
|
25
57
|
|
|
26
58
|
bindStart (ctx) {
|
|
@@ -38,6 +70,11 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
38
70
|
[CLIENT_PORT_KEY]: ctx.conf.port,
|
|
39
71
|
},
|
|
40
72
|
}, ctx)
|
|
73
|
+
|
|
74
|
+
if (ctx.poolWaitTime !== undefined) {
|
|
75
|
+
span.setTag(`${this.component}.pool.wait_time`, ctx.poolWaitTime)
|
|
76
|
+
}
|
|
77
|
+
|
|
41
78
|
ctx.sql = this.injectDbmQuery(span, ctx.sql, service.name)
|
|
42
79
|
|
|
43
80
|
return ctx.currentStore
|
|
@@ -145,7 +145,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
configure (config) {
|
|
148
|
-
return super.configure(normalizeConfig(config))
|
|
148
|
+
return super.configure(web.normalizeConfig(config))
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -178,22 +178,4 @@ function setHttpParentRoute (span, method, page, isStatic) {
|
|
|
178
178
|
span.setTag(RESOURCE_NAME, `${method} ${page}`.trim())
|
|
179
179
|
nextParentRoutes.set(span, page)
|
|
180
180
|
}
|
|
181
|
-
|
|
182
|
-
function normalizeConfig (config) {
|
|
183
|
-
const hooks = getHooks(config)
|
|
184
|
-
const validateStatus = typeof config.validateStatus === 'function'
|
|
185
|
-
? config.validateStatus
|
|
186
|
-
: code => code < 500
|
|
187
|
-
|
|
188
|
-
return { ...config, hooks, validateStatus }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const noop = () => {}
|
|
192
|
-
|
|
193
|
-
function getHooks (config) {
|
|
194
|
-
const request = config.hooks?.request ?? noop
|
|
195
|
-
|
|
196
|
-
return { request }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
181
|
module.exports = NextPlugin
|
|
@@ -15,6 +15,9 @@ const { AGENTS_ERROR_TYPE, applyError, deriveSpanName } = require('./util')
|
|
|
15
15
|
|
|
16
16
|
const COMPONENT = 'openai-agents'
|
|
17
17
|
const DEFAULT_MODEL_PROVIDER = 'openai'
|
|
18
|
+
// Bounds the agents-core parent-chain walk so a cyclic chain from a future
|
|
19
|
+
// agents-core version can't spin on the span-start hot path.
|
|
20
|
+
const MAX_ANCESTOR_WALK = 32
|
|
18
21
|
const MODEL_BASE_URL_STORE_KEY = Symbol('openai-agents.model-base-url')
|
|
19
22
|
const legacyStorage = storage('legacy')
|
|
20
23
|
|
|
@@ -37,9 +40,20 @@ const KIND_TO_SPAN_KIND = {
|
|
|
37
40
|
* metadata?: Record<string, unknown>,
|
|
38
41
|
* groupId?: string,
|
|
39
42
|
* llmobsParentStore?: object,
|
|
43
|
+
* activeSpanCount: number,
|
|
44
|
+
* completionRequested: boolean,
|
|
40
45
|
* }} LLMObsTraceInfo
|
|
41
46
|
*/
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {{
|
|
50
|
+
* parentId: string | null,
|
|
51
|
+
* traceId?: string,
|
|
52
|
+
* activeChildCount: number,
|
|
53
|
+
* ended: boolean,
|
|
54
|
+
* }} UntracedSpanInfo
|
|
55
|
+
*/
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* Owns tracer/tagger refs, maps agents-core span ids → dd-trace spans, and
|
|
45
59
|
* reconstructs workflow-level input/output from the first and last response
|
|
@@ -59,6 +73,12 @@ class OpenAIAgentsIntegration {
|
|
|
59
73
|
#tagger
|
|
60
74
|
/** @type {Map<string, import('../../dd-trace/src/opentracing/span')>} */
|
|
61
75
|
#oaiToDdSpan = new Map()
|
|
76
|
+
/**
|
|
77
|
+
* agents-core spans we deliberately don't trace (`task` / `turn`). Ended
|
|
78
|
+
* nodes stay walkable only while an observed child callback is still active.
|
|
79
|
+
* @type {Map<string, UntracedSpanInfo>}
|
|
80
|
+
*/
|
|
81
|
+
#untracedSpans = new Map()
|
|
62
82
|
/** @type {Map<string, LLMObsTraceInfo>} */
|
|
63
83
|
#traceInfo = new Map()
|
|
64
84
|
|
|
@@ -83,11 +103,58 @@ class OpenAIAgentsIntegration {
|
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
/**
|
|
86
|
-
*
|
|
106
|
+
* Resolve the dd-trace span for an agents-core spanId. When the id belongs to
|
|
107
|
+
* a span type we don't trace — agents-core >=0.14 runs model calls inside a
|
|
108
|
+
* `turn` span — the nearest traced ancestor's span is returned so callers
|
|
109
|
+
* still activate the enclosing agent span.
|
|
110
|
+
*
|
|
111
|
+
* @param {string | undefined | null} spanId agents-core spanId
|
|
87
112
|
* @returns {import('../../dd-trace/src/opentracing/span') | undefined}
|
|
88
113
|
*/
|
|
89
114
|
getDDSpan (spanId) {
|
|
90
|
-
return this.#oaiToDdSpan.get(spanId)
|
|
115
|
+
return this.#oaiToDdSpan.get(this.#nearestTracedAncestorId(spanId))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remember an untraced agents-core span's parent so the chain stays walkable.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} oaiSpan
|
|
122
|
+
*/
|
|
123
|
+
recordUntracedSpan (oaiSpan) {
|
|
124
|
+
const { spanId } = oaiSpan
|
|
125
|
+
if (!spanId || this.#untracedSpans.has(spanId)) return
|
|
126
|
+
|
|
127
|
+
const parentId = oaiSpan.parentId ?? null
|
|
128
|
+
this.#untracedSpans.set(spanId, {
|
|
129
|
+
parentId,
|
|
130
|
+
traceId: oaiSpan.traceId,
|
|
131
|
+
activeChildCount: 0,
|
|
132
|
+
ended: false,
|
|
133
|
+
})
|
|
134
|
+
this.#retainUntracedParent(parentId)
|
|
135
|
+
this.#spanStarted(oaiSpan.traceId)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Mark an untraced span callback complete. Its ancestry remains available
|
|
140
|
+
* until every observed child callback has completed.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} oaiSpan
|
|
143
|
+
*/
|
|
144
|
+
endUntracedSpan (oaiSpan) {
|
|
145
|
+
const info = this.#untracedSpans.get(oaiSpan.spanId)
|
|
146
|
+
if (!info || info.ended) return
|
|
147
|
+
|
|
148
|
+
// agents-core does not end its trace when the run throws. In 0.14 the
|
|
149
|
+
// failure can live on the root task before any agent span exists, making
|
|
150
|
+
// this structural callback the only opportunity to finish the workflow.
|
|
151
|
+
if (oaiSpan.error && this.#isTopLevelSpan(oaiSpan)) {
|
|
152
|
+
this.#requestWorkflowCompletion(oaiSpan.traceId, oaiSpan)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
info.ended = true
|
|
156
|
+
this.#pruneUntracedSpan(oaiSpan.spanId, info)
|
|
157
|
+
this.#spanEnded(info.traceId)
|
|
91
158
|
}
|
|
92
159
|
|
|
93
160
|
/**
|
|
@@ -114,6 +181,7 @@ class OpenAIAgentsIntegration {
|
|
|
114
181
|
ddSpan.finish()
|
|
115
182
|
}
|
|
116
183
|
this.#oaiToDdSpan.clear()
|
|
184
|
+
this.#untracedSpans.clear()
|
|
117
185
|
this.#traceInfo.clear()
|
|
118
186
|
}
|
|
119
187
|
|
|
@@ -138,6 +206,8 @@ class OpenAIAgentsIntegration {
|
|
|
138
206
|
groupId: oaiTrace.groupId || undefined,
|
|
139
207
|
metadata: oaiTrace.metadata,
|
|
140
208
|
llmobsParentStore,
|
|
209
|
+
activeSpanCount: 0,
|
|
210
|
+
completionRequested: false,
|
|
141
211
|
})
|
|
142
212
|
|
|
143
213
|
this.#tagger.registerLLMObsSpan(ddSpan, {
|
|
@@ -153,38 +223,55 @@ class OpenAIAgentsIntegration {
|
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
endTrace (oaiTrace) {
|
|
156
|
-
this.#
|
|
226
|
+
this.#requestWorkflowCompletion(oaiTrace.traceId)
|
|
157
227
|
}
|
|
158
228
|
|
|
159
229
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `rootAgentSpan` is provided, its `error` field is reflected onto the
|
|
164
|
-
* workflow span before finishing.
|
|
230
|
+
* Request workflow completion after all observed span callbacks finish.
|
|
231
|
+
* Error tags are applied immediately so the agents-core span does not need
|
|
232
|
+
* to be retained while completion is pending.
|
|
165
233
|
*
|
|
166
234
|
* @param {string | undefined} traceId
|
|
167
|
-
* @param {object} [
|
|
235
|
+
* @param {object} [rootSpan] - top-level oai-span that ended in error.
|
|
168
236
|
*/
|
|
169
|
-
#
|
|
237
|
+
#requestWorkflowCompletion (traceId, rootSpan) {
|
|
170
238
|
if (!traceId) return
|
|
171
239
|
const ddSpan = this.#oaiToDdSpan.get(traceId)
|
|
172
240
|
if (!ddSpan) return
|
|
173
241
|
const info = this.#traceInfo.get(traceId)
|
|
174
242
|
|
|
175
|
-
if (
|
|
243
|
+
if (rootSpan?.error) {
|
|
176
244
|
ddSpan.setTag('error', true)
|
|
177
245
|
ddSpan.setTag('error.type', AGENTS_ERROR_TYPE)
|
|
178
|
-
if (
|
|
179
|
-
ddSpan.setTag('error.message',
|
|
246
|
+
if (rootSpan.error.message) {
|
|
247
|
+
ddSpan.setTag('error.message', rootSpan.error.message)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (info) {
|
|
252
|
+
if (!info.completionRequested) {
|
|
253
|
+
info.completionRequested = true
|
|
254
|
+
if (LLMObsTagger.tagMap.has(ddSpan)) {
|
|
255
|
+
llmobsStorage.enterWith(info.llmobsParentStore)
|
|
256
|
+
}
|
|
180
257
|
}
|
|
258
|
+
if (info.activeSpanCount > 0) return
|
|
181
259
|
}
|
|
182
260
|
|
|
261
|
+
this.#completeWorkflowSpan(traceId)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Finish the workflow dd-trace span and clear its bookkeeping.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} traceId
|
|
268
|
+
*/
|
|
269
|
+
#completeWorkflowSpan (traceId) {
|
|
270
|
+
const ddSpan = this.#oaiToDdSpan.get(traceId)
|
|
271
|
+
if (!ddSpan) return
|
|
272
|
+
|
|
183
273
|
if (this.#isLLMObsEnabled()) this.#setTraceAttributes(ddSpan, traceId)
|
|
184
274
|
ddSpan.finish()
|
|
185
|
-
if (info && LLMObsTagger.tagMap.has(ddSpan)) {
|
|
186
|
-
llmobsStorage.enterWith(info.llmobsParentStore)
|
|
187
|
-
}
|
|
188
275
|
this.#oaiToDdSpan.delete(traceId)
|
|
189
276
|
this.#traceInfo.delete(traceId)
|
|
190
277
|
}
|
|
@@ -204,6 +291,8 @@ class OpenAIAgentsIntegration {
|
|
|
204
291
|
})
|
|
205
292
|
|
|
206
293
|
this.#oaiToDdSpan.set(spanId, ddSpan)
|
|
294
|
+
this.#retainUntracedParent(oaiSpan.parentId)
|
|
295
|
+
this.#spanStarted(oaiSpan.traceId)
|
|
207
296
|
|
|
208
297
|
if (this.#isLLMObsEnabled()) {
|
|
209
298
|
const llmobsOptions = {
|
|
@@ -230,49 +319,57 @@ class OpenAIAgentsIntegration {
|
|
|
230
319
|
const ddSpan = this.#oaiToDdSpan.get(spanId)
|
|
231
320
|
if (!ddSpan) return
|
|
232
321
|
|
|
233
|
-
|
|
322
|
+
try {
|
|
323
|
+
applyError(ddSpan, oaiSpan)
|
|
234
324
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
325
|
+
if (oaiSpan.spanData?.type === 'handoff') {
|
|
326
|
+
const spanName = deriveSpanName(oaiSpan)
|
|
327
|
+
ddSpan.setOperationName(spanName)
|
|
328
|
+
if (this.#isLLMObsEnabled()) this.#tagger.setName(ddSpan, spanName)
|
|
329
|
+
}
|
|
240
330
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
331
|
+
if (this.#isLLMObsEnabled()) {
|
|
332
|
+
const spanData = oaiSpan.spanData
|
|
333
|
+
switch (spanData?.type) {
|
|
334
|
+
case 'response':
|
|
335
|
+
this.#setResponseAttributes(ddSpan, oaiSpan)
|
|
336
|
+
this.#updateTraceInfoOutput(oaiSpan)
|
|
337
|
+
break
|
|
338
|
+
case 'generation':
|
|
339
|
+
this.#setGenerationAttributes(ddSpan, oaiSpan)
|
|
340
|
+
this.#updateTraceInfoOutput(oaiSpan)
|
|
341
|
+
break
|
|
342
|
+
case 'function':
|
|
343
|
+
this.#tagger.tagTextIO(ddSpan, spanData.input ?? '', spanData.output ?? '')
|
|
344
|
+
break
|
|
345
|
+
case 'handoff':
|
|
346
|
+
this.#tagger.tagTextIO(ddSpan, spanData.from_agent ?? '', spanData.to_agent ?? '')
|
|
347
|
+
break
|
|
348
|
+
case 'agent':
|
|
349
|
+
this.#setAgentAttributes(ddSpan, oaiSpan)
|
|
350
|
+
break
|
|
351
|
+
case 'custom':
|
|
352
|
+
if (spanData.data && typeof spanData.data === 'object') {
|
|
353
|
+
this.#tagger.tagMetadata(ddSpan, spanData.data)
|
|
354
|
+
}
|
|
355
|
+
break
|
|
356
|
+
}
|
|
266
357
|
}
|
|
267
|
-
}
|
|
268
358
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
359
|
+
// agents-core's withTrace skips Trace.end() when its callback throws, so
|
|
360
|
+
// an errored top-level span is our last chance to finalize the workflow.
|
|
361
|
+
if (oaiSpan.error && this.#isTopLevelSpan(oaiSpan)) {
|
|
362
|
+
this.#requestWorkflowCompletion(oaiSpan.traceId, oaiSpan)
|
|
363
|
+
}
|
|
364
|
+
} finally {
|
|
365
|
+
try {
|
|
366
|
+
ddSpan.finish()
|
|
367
|
+
} finally {
|
|
368
|
+
this.#oaiToDdSpan.delete(spanId)
|
|
369
|
+
this.#releaseUntracedParent(oaiSpan.parentId)
|
|
370
|
+
this.#spanEnded(oaiSpan.traceId)
|
|
371
|
+
}
|
|
274
372
|
}
|
|
275
|
-
this.#oaiToDdSpan.delete(spanId)
|
|
276
373
|
}
|
|
277
374
|
|
|
278
375
|
// ── Per-type attribute setters ──────────────────────────────────────────────
|
|
@@ -360,7 +457,7 @@ class OpenAIAgentsIntegration {
|
|
|
360
457
|
#llmSpanParentAgentName (oaiSpan) {
|
|
361
458
|
const traceInfo = this.#traceInfo.get(oaiSpan.traceId)
|
|
362
459
|
if (!traceInfo?.currentTopLevelAgentSpanId) return
|
|
363
|
-
if (oaiSpan.parentId
|
|
460
|
+
if (!this.#hasUntracedPathToAncestor(oaiSpan.parentId, traceInfo.currentTopLevelAgentSpanId)) return
|
|
364
461
|
return traceInfo.currentTopLevelAgentName
|
|
365
462
|
}
|
|
366
463
|
|
|
@@ -412,13 +509,12 @@ class OpenAIAgentsIntegration {
|
|
|
412
509
|
const info = this.#traceInfo.get(oaiSpan.traceId)
|
|
413
510
|
if (!info) return
|
|
414
511
|
|
|
415
|
-
const parentId = oaiSpan.parentId
|
|
416
512
|
const type = oaiSpan.spanData?.type
|
|
417
513
|
|
|
418
514
|
// Identify the first top-level agent span under the root trace and
|
|
419
515
|
// stash its display name so `${agentName} (LLM)` doesn't have to read
|
|
420
516
|
// the dd-trace span context's private fields later.
|
|
421
|
-
if (type === 'agent' &&
|
|
517
|
+
if (type === 'agent' && this.#isTopLevelSpan(oaiSpan)) {
|
|
422
518
|
info.currentTopLevelAgentSpanId = oaiSpan.spanId
|
|
423
519
|
info.currentTopLevelAgentName = spanName
|
|
424
520
|
}
|
|
@@ -427,9 +523,9 @@ class OpenAIAgentsIntegration {
|
|
|
427
523
|
// as the workflow-level input source.
|
|
428
524
|
if (
|
|
429
525
|
(type === 'response' || type === 'generation') &&
|
|
430
|
-
|
|
526
|
+
info.currentTopLevelAgentSpanId &&
|
|
431
527
|
!info.inputOaiSpan &&
|
|
432
|
-
parentId
|
|
528
|
+
this.#hasUntracedPathToAncestor(oaiSpan.parentId, info.currentTopLevelAgentSpanId)
|
|
433
529
|
) {
|
|
434
530
|
info.inputOaiSpan = oaiSpan
|
|
435
531
|
}
|
|
@@ -440,9 +536,8 @@ class OpenAIAgentsIntegration {
|
|
|
440
536
|
if (!info) return
|
|
441
537
|
|
|
442
538
|
if (
|
|
443
|
-
oaiSpan.parentId &&
|
|
444
539
|
info.currentTopLevelAgentSpanId &&
|
|
445
|
-
oaiSpan.parentId
|
|
540
|
+
this.#hasUntracedPathToAncestor(oaiSpan.parentId, info.currentTopLevelAgentSpanId)
|
|
446
541
|
) {
|
|
447
542
|
info.outputOaiSpan = oaiSpan
|
|
448
543
|
}
|
|
@@ -451,18 +546,132 @@ class OpenAIAgentsIntegration {
|
|
|
451
546
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
452
547
|
|
|
453
548
|
#resolveParent (oaiSpan) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
549
|
+
return this.getDDSpan(oaiSpan.parentId) ?? this.#oaiToDdSpan.get(oaiSpan.traceId)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Walk the agents-core parent chain from `spanId`, skipping spans we don't
|
|
554
|
+
* trace, and return the first id that maps to a dd-trace span. Returns
|
|
555
|
+
* `undefined` when the walk reaches the trace root (or a span we never saw),
|
|
556
|
+
* which callers read as "no traced ancestor".
|
|
557
|
+
*
|
|
558
|
+
* @param {string | undefined | null} spanId agents-core spanId to start from
|
|
559
|
+
* @returns {string | undefined}
|
|
560
|
+
*/
|
|
561
|
+
#nearestTracedAncestorId (spanId) {
|
|
562
|
+
let currentId = spanId
|
|
563
|
+
for (let depth = 0; currentId != null && depth < MAX_ANCESTOR_WALK; depth++) {
|
|
564
|
+
if (this.#oaiToDdSpan.has(currentId)) return currentId
|
|
565
|
+
currentId = this.#untracedSpans.get(currentId)?.parentId
|
|
459
566
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* True when `spanId` reaches `ancestorId` through only untraced structural
|
|
571
|
+
* spans. Unlike #nearestTracedAncestorId, this still recognizes an ancestor
|
|
572
|
+
* whose dd-trace span has already finished and been removed from the live map.
|
|
573
|
+
*
|
|
574
|
+
* @param {string | undefined | null} spanId agents-core spanId to start from
|
|
575
|
+
* @param {string} ancestorId agents-core ancestor spanId
|
|
576
|
+
* @returns {boolean}
|
|
577
|
+
*/
|
|
578
|
+
#hasUntracedPathToAncestor (spanId, ancestorId) {
|
|
579
|
+
let currentId = spanId
|
|
580
|
+
for (let depth = 0; currentId != null && depth < MAX_ANCESTOR_WALK; depth++) {
|
|
581
|
+
if (currentId === ancestorId) return true
|
|
582
|
+
currentId = this.#untracedSpans.get(currentId)?.parentId
|
|
583
|
+
}
|
|
584
|
+
return false
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* True when nothing but untraced spans sits between this span and the trace
|
|
589
|
+
* root. Stricter than `#nearestTracedAncestorId(…) === undefined`, which also
|
|
590
|
+
* answers "undefined" for a parent we simply never saw — that must not be
|
|
591
|
+
* mistaken for top-level, or an error would finalize the workflow span early.
|
|
592
|
+
*
|
|
593
|
+
* @param {object} oaiSpan
|
|
594
|
+
* @returns {boolean}
|
|
595
|
+
*/
|
|
596
|
+
#isTopLevelSpan (oaiSpan) {
|
|
597
|
+
let currentId = oaiSpan.parentId
|
|
598
|
+
for (let depth = 0; depth < MAX_ANCESTOR_WALK; depth++) {
|
|
599
|
+
if (currentId == null) return true
|
|
600
|
+
const info = this.#untracedSpans.get(currentId)
|
|
601
|
+
if (!info) return false
|
|
602
|
+
currentId = info.parentId
|
|
603
|
+
}
|
|
604
|
+
return false
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Keep a structural parent alive until this child callback completes.
|
|
609
|
+
*
|
|
610
|
+
* @param {string | undefined | null} parentId
|
|
611
|
+
*/
|
|
612
|
+
#retainUntracedParent (parentId) {
|
|
613
|
+
const info = this.#untracedSpans.get(parentId)
|
|
614
|
+
if (info) info.activeChildCount++
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Release a structural parent and iteratively prune any ended, childless
|
|
619
|
+
* ancestors. Each iteration deletes one node, so even a cyclic parent chain
|
|
620
|
+
* terminates when it reaches the first node it already removed.
|
|
621
|
+
*
|
|
622
|
+
* @param {string | undefined | null} parentId
|
|
623
|
+
*/
|
|
624
|
+
#releaseUntracedParent (parentId) {
|
|
625
|
+
let currentId = parentId
|
|
626
|
+
while (currentId != null) {
|
|
627
|
+
const info = this.#untracedSpans.get(currentId)
|
|
628
|
+
if (!info || info.activeChildCount === 0) return
|
|
629
|
+
|
|
630
|
+
info.activeChildCount--
|
|
631
|
+
if (!info.ended || info.activeChildCount > 0) return
|
|
632
|
+
|
|
633
|
+
this.#untracedSpans.delete(currentId)
|
|
634
|
+
currentId = info.parentId
|
|
463
635
|
}
|
|
464
636
|
}
|
|
465
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Remove an ended structural span once no observed child still needs it.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} spanId
|
|
642
|
+
* @param {UntracedSpanInfo} info
|
|
643
|
+
*/
|
|
644
|
+
#pruneUntracedSpan (spanId, info) {
|
|
645
|
+
if (info.activeChildCount > 0) return
|
|
646
|
+
this.#untracedSpans.delete(spanId)
|
|
647
|
+
this.#releaseUntracedParent(info.parentId)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Count a span callback whose end may arrive after trace completion.
|
|
652
|
+
*
|
|
653
|
+
* @param {string | undefined} traceId
|
|
654
|
+
*/
|
|
655
|
+
#spanStarted (traceId) {
|
|
656
|
+
const info = this.#traceInfo.get(traceId)
|
|
657
|
+
if (info) info.activeSpanCount++
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Release one observed span callback and complete the trace when it becomes
|
|
662
|
+
* quiescent. Structural ancestry prunes independently with each subtree.
|
|
663
|
+
*
|
|
664
|
+
* @param {string | undefined} traceId
|
|
665
|
+
*/
|
|
666
|
+
#spanEnded (traceId) {
|
|
667
|
+
const info = this.#traceInfo.get(traceId)
|
|
668
|
+
if (!info) return
|
|
669
|
+
if (info.activeSpanCount > 0) info.activeSpanCount--
|
|
670
|
+
if (info.activeSpanCount > 0) return
|
|
671
|
+
|
|
672
|
+
if (info.completionRequested) this.#completeWorkflowSpan(traceId)
|
|
673
|
+
}
|
|
674
|
+
|
|
466
675
|
/**
|
|
467
676
|
* Read the mutable tracer configuration so remote/runtime enablement is
|
|
468
677
|
* reflected without reconstructing the plugin.
|
|
@@ -66,10 +66,16 @@ class DDOpenAIAgentsProcessor {
|
|
|
66
66
|
const integration = this._getIntegration()
|
|
67
67
|
if (!integration?.enabled) return RESOLVED
|
|
68
68
|
if (!oaiSpan?.spanData) return RESOLVED // guard NoopSpan
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
const type = oaiSpan.spanData.type
|
|
70
|
+
const kind = SPAN_KIND_BY_TYPE[type]
|
|
71
71
|
try {
|
|
72
|
-
|
|
72
|
+
if (kind) {
|
|
73
|
+
integration.startSpan(oaiSpan, kind)
|
|
74
|
+
} else if (type === 'task' || type === 'turn') {
|
|
75
|
+
// agents-core >=0.14 nests real work under these structural spans, so
|
|
76
|
+
// their ancestry still has to be walkable for parent resolution.
|
|
77
|
+
integration.recordUntracedSpan(oaiSpan)
|
|
78
|
+
}
|
|
73
79
|
} catch (err) {
|
|
74
80
|
log.warn('[openai-agents] onSpanStart failed: %s', err)
|
|
75
81
|
}
|
|
@@ -80,8 +86,14 @@ class DDOpenAIAgentsProcessor {
|
|
|
80
86
|
const integration = this._getIntegration()
|
|
81
87
|
if (!integration?.enabled) return RESOLVED
|
|
82
88
|
if (!oaiSpan?.spanData) return RESOLVED
|
|
89
|
+
const type = oaiSpan.spanData.type
|
|
90
|
+
const kind = SPAN_KIND_BY_TYPE[type]
|
|
83
91
|
try {
|
|
84
|
-
|
|
92
|
+
if (kind) {
|
|
93
|
+
integration.endSpan(oaiSpan)
|
|
94
|
+
} else if (type === 'task' || type === 'turn') {
|
|
95
|
+
integration.endUntracedSpan(oaiSpan)
|
|
96
|
+
}
|
|
85
97
|
} catch (err) {
|
|
86
98
|
log.warn('[openai-agents] onSpanEnd failed: %s', err)
|
|
87
99
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
|
+
const { writeDatadogParentId, writeDatadogTraceId } = require('../../dd-trace/src/carrier')
|
|
5
6
|
|
|
6
7
|
const {
|
|
7
8
|
TEST_STATUS,
|
|
@@ -13,7 +14,6 @@ const {
|
|
|
13
14
|
getTestSuiteCommonTags,
|
|
14
15
|
getTestLevelsMetadataTags,
|
|
15
16
|
getTestSessionName,
|
|
16
|
-
getIsFaultyEarlyFlakeDetection,
|
|
17
17
|
TEST_SOURCE_FILE,
|
|
18
18
|
TEST_IS_RETRY,
|
|
19
19
|
TEST_CODE_OWNERS,
|
|
@@ -107,19 +107,6 @@ class VitestPlugin extends CiPlugin {
|
|
|
107
107
|
})
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
-
this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
|
|
111
|
-
knownTests,
|
|
112
|
-
testFilepaths,
|
|
113
|
-
onDone,
|
|
114
|
-
}) => {
|
|
115
|
-
const isFaulty = getIsFaultyEarlyFlakeDetection(
|
|
116
|
-
testFilepaths.map(testFilepath => getTestSuitePath(testFilepath, this.repositoryRoot)),
|
|
117
|
-
knownTests,
|
|
118
|
-
this.libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
119
|
-
)
|
|
120
|
-
onDone(isFaulty)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
110
|
this.addBind('ci:vitest:test:start', (ctx) => {
|
|
124
111
|
const {
|
|
125
112
|
testName,
|
|
@@ -424,12 +411,13 @@ class VitestPlugin extends CiPlugin {
|
|
|
424
411
|
this._setRepositoryRoot(repositoryRoot, codeOwnersEntries)
|
|
425
412
|
this.command = testCommand
|
|
426
413
|
this.frameworkVersion = frameworkVersion
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
414
|
+
let testSessionSpanContext
|
|
415
|
+
if (testSessionId && testModuleId) {
|
|
416
|
+
const carrier = /** @type {Record<string, unknown>} */ ({})
|
|
417
|
+
writeDatadogTraceId(carrier, testSessionId)
|
|
418
|
+
writeDatadogParentId(carrier, testModuleId)
|
|
419
|
+
testSessionSpanContext = this.tracer.extract('text_map', carrier)
|
|
420
|
+
}
|
|
433
421
|
|
|
434
422
|
const trimmedCommand = DD_MAJOR < 6 ? this.command : 'vitest run'
|
|
435
423
|
// test suites run in a different process, so they also need to init the metadata dictionary
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { readDatadogTraceId, readTraceparent } = require('../../dd-trace/src/carrier')
|
|
3
4
|
const DatadogSpanContext = require('../../dd-trace/src/opentracing/span_context')
|
|
4
5
|
|
|
5
6
|
const TRACE_ID_UPPER_TAG = '_dd.p.tid'
|
|
@@ -38,7 +39,7 @@ function createWebSocketSpanContext (spanContext) {
|
|
|
38
39
|
* @returns {boolean}
|
|
39
40
|
*/
|
|
40
41
|
function hasTraceHeaders (headers) {
|
|
41
|
-
return !!(headers && (headers
|
|
42
|
+
return !!(headers && (readDatadogTraceId(headers) || readTraceparent(headers)))
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|