dd-trace 5.106.0 → 5.107.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/index.d.ts +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber.js +7 -0
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +1 -2
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -206,6 +206,32 @@ function wrapConnection (Connection, version) {
|
|
|
206
206
|
})
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* mysql2 defers a busy pool's `getConnection` callback and runs it in the releasing connection's
|
|
211
|
+
* async context; capture the caller's context and restore it around the callback so spans created
|
|
212
|
+
* by the queued query attach to the caller rather than the previous query that freed the connection.
|
|
213
|
+
*
|
|
214
|
+
* @param {Function} Pool
|
|
215
|
+
* @returns {Function}
|
|
216
|
+
*/
|
|
217
|
+
function wrapGetConnection (Pool) {
|
|
218
|
+
const connectionStartCh = channel('apm:mysql2:connection:start')
|
|
219
|
+
const connectionFinishCh = channel('apm:mysql2:connection:finish')
|
|
220
|
+
|
|
221
|
+
shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
|
|
222
|
+
const ctx = {}
|
|
223
|
+
arguments[0] = function (...args) {
|
|
224
|
+
return connectionFinishCh.runStores(ctx, cb, this, ...args)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
connectionStartCh.publish(ctx)
|
|
228
|
+
|
|
229
|
+
return getConnection.apply(this, arguments)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
return Pool
|
|
233
|
+
}
|
|
234
|
+
|
|
209
235
|
/**
|
|
210
236
|
* @param {Function} Pool
|
|
211
237
|
* @param {string} version
|
|
@@ -215,6 +241,8 @@ function wrapPool (Pool, version) {
|
|
|
215
241
|
const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
|
|
216
242
|
const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
|
|
217
243
|
|
|
244
|
+
wrapGetConnection(Pool)
|
|
245
|
+
|
|
218
246
|
shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
|
|
219
247
|
if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
|
|
220
248
|
|
|
@@ -373,6 +401,12 @@ addHook(
|
|
|
373
401
|
/** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapPool)
|
|
374
402
|
)
|
|
375
403
|
|
|
404
|
+
// mysql2 >=3.11.5 moved the pool onto BasePool in lib/base/pool.js.
|
|
405
|
+
addHook(
|
|
406
|
+
{ name: 'mysql2', file: 'lib/base/pool.js', versions: ['>=3.11.5'] },
|
|
407
|
+
/** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapGetConnection)
|
|
408
|
+
)
|
|
409
|
+
|
|
376
410
|
// PoolNamespace.prototype.query does not exist in mysql2<2.3.0
|
|
377
411
|
addHook(
|
|
378
412
|
{ name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['2.3.0 - 3.11.4'] },
|
|
@@ -49,20 +49,22 @@ addHook({ name: 'net' }, (net) => {
|
|
|
49
49
|
|
|
50
50
|
const emit = this.emit
|
|
51
51
|
let pendingReadyEvents = 2
|
|
52
|
-
|
|
52
|
+
// Named `emit`/arity-1 mirrors the socket method so the per-socket wrap
|
|
53
|
+
// skips its name/length rewrite.
|
|
54
|
+
this.emit = shimmer.wrapFunction(emit, originalEmit => function emit (eventName) {
|
|
53
55
|
switch (eventName) {
|
|
54
56
|
case 'ready':
|
|
55
57
|
case 'connect':
|
|
56
|
-
if (--pendingReadyEvents === 0) this.emit =
|
|
58
|
+
if (--pendingReadyEvents === 0) this.emit = originalEmit
|
|
57
59
|
return readyCh.runStores(ctx, () => {
|
|
58
|
-
return
|
|
60
|
+
return Reflect.apply(originalEmit, this, arguments)
|
|
59
61
|
})
|
|
60
62
|
case 'error':
|
|
61
63
|
case 'close':
|
|
62
|
-
this.emit =
|
|
63
|
-
return
|
|
64
|
+
this.emit = originalEmit
|
|
65
|
+
return Reflect.apply(originalEmit, this, arguments)
|
|
64
66
|
default:
|
|
65
|
-
return
|
|
67
|
+
return Reflect.apply(originalEmit, this, arguments)
|
|
66
68
|
}
|
|
67
69
|
})
|
|
68
70
|
|
|
@@ -240,6 +240,10 @@ for (const extension of extensions) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
return ch.start.runStores(ctx, () => {
|
|
243
|
+
// Explicit childOf rather than async-context: the _thenUnwrap/parse path
|
|
244
|
+
// decouples the lazy evaluation from the active scope at call time.
|
|
245
|
+
if (guard) guard.parentSpan = ctx.currentStore?.span
|
|
246
|
+
|
|
243
247
|
const apiProm = methodFn.apply(this, args)
|
|
244
248
|
|
|
245
249
|
if (baseResource === 'chat.completions' && typeof apiProm._thenUnwrap === 'function') {
|
|
@@ -300,23 +304,31 @@ function handleUnwrappedAPIPromise (apiProm, ctx, stream, guard) {
|
|
|
300
304
|
return body
|
|
301
305
|
}
|
|
302
306
|
|
|
303
|
-
|
|
307
|
+
const responseData = {
|
|
304
308
|
headers: response.headers,
|
|
305
309
|
data: body,
|
|
306
310
|
request: {
|
|
307
311
|
path: response.url,
|
|
308
312
|
method: options.method,
|
|
309
313
|
},
|
|
310
|
-
}
|
|
314
|
+
}
|
|
311
315
|
|
|
312
|
-
if (!guard)
|
|
316
|
+
if (!guard) {
|
|
317
|
+
finish(ctx, responseData)
|
|
318
|
+
return body
|
|
319
|
+
}
|
|
313
320
|
|
|
314
|
-
|
|
321
|
+
// Finish after evaluation so a block propagates the error to openai.request
|
|
322
|
+
// and the span wraps its ai_guard child instead of closing before it.
|
|
323
|
+
return aiGuard.evaluateOutput(guard, body).then(() => {
|
|
324
|
+
finish(ctx, responseData)
|
|
325
|
+
return body
|
|
326
|
+
})
|
|
315
327
|
})
|
|
316
328
|
.catch(error => {
|
|
317
|
-
// ctx.result is set inside finish(); if absent, finish never ran (sync throw in
|
|
318
|
-
//
|
|
319
|
-
// If finish already ran
|
|
329
|
+
// ctx.result is set inside finish(); if absent, finish never ran (sync throw in the success
|
|
330
|
+
// branch, Before Model block, After Model block, or openai error) — record the error now so
|
|
331
|
+
// the openai.request span is marked errored. If finish already ran, don't double-publish.
|
|
320
332
|
if (!ctx.result) finish(ctx, undefined, error)
|
|
321
333
|
throw error
|
|
322
334
|
})
|
|
@@ -15,6 +15,9 @@ const errorCh = channel('apm:pg:query:error')
|
|
|
15
15
|
const startPoolQueryCh = channel('datadog:pg:pool:query:start')
|
|
16
16
|
const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
|
|
17
17
|
|
|
18
|
+
const poolConnectStartCh = channel('apm:pg:pool:connect:start')
|
|
19
|
+
const poolConnectFinishCh = channel('apm:pg:pool:connect:finish')
|
|
20
|
+
|
|
18
21
|
// Drivers like pg-promise reuse the same prepared-statement query object across executions; cache
|
|
19
22
|
// the un-injected `text` so the wrap doesn't capture a previous DBM injection as the new original.
|
|
20
23
|
const originalTextCache = new WeakMap()
|
|
@@ -30,6 +33,22 @@ addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/client.js' }, Client =>
|
|
|
30
33
|
})
|
|
31
34
|
|
|
32
35
|
addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
|
|
36
|
+
// pg defers a busy pool's connect callback and runs it in the releasing query's async context;
|
|
37
|
+
// capture the caller's context and restore it around the callback so spans attach to the caller.
|
|
38
|
+
shimmer.wrap(pg.Pool.prototype, 'connect', connect => function (cb) {
|
|
39
|
+
if (typeof cb !== 'function' || !poolConnectStartCh.hasSubscribers) {
|
|
40
|
+
return connect.apply(this, arguments)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ctx = {}
|
|
44
|
+
arguments[0] = function (...args) {
|
|
45
|
+
return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
poolConnectStartCh.publish(ctx)
|
|
49
|
+
|
|
50
|
+
return connect.apply(this, arguments)
|
|
51
|
+
})
|
|
33
52
|
shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
|
|
34
53
|
return pg
|
|
35
54
|
})
|
|
@@ -157,9 +157,9 @@ function createWrapRouterMethod (name, compile) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function wrapNext (req, originalNext) {
|
|
160
|
-
// Per layer dispatch, N per request. `
|
|
161
|
-
//
|
|
162
|
-
return shimmer.wrapCallback(originalNext,
|
|
160
|
+
// Per layer dispatch, N per request. Named `next`/arity-1 mirrors the
|
|
161
|
+
// router continuation so wrapCallback skips its name/length rewrite.
|
|
162
|
+
return shimmer.wrapCallback(originalNext, original => function next (error) {
|
|
163
163
|
if (error && error !== 'route' && error !== 'router') {
|
|
164
164
|
errorChannel.publish({ req, error })
|
|
165
165
|
}
|
|
@@ -167,7 +167,7 @@ function createWrapRouterMethod (name, compile) {
|
|
|
167
167
|
nextChannel.publish({ req })
|
|
168
168
|
finishChannel.publish({ req })
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
original.apply(this, arguments)
|
|
171
171
|
})
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -328,7 +328,8 @@ const routerParamStartCh = channel('datadog:router:param:start')
|
|
|
328
328
|
const visitedParams = new WeakSet()
|
|
329
329
|
|
|
330
330
|
function wrapHandleRequest (original) {
|
|
331
|
-
return function wrappedHandleRequest (
|
|
331
|
+
return function wrappedHandleRequest (...args) {
|
|
332
|
+
const req = args[0]
|
|
332
333
|
if (routerParamStartCh.hasSubscribers && !visitedParams.has(req.params) && Object.keys(req.params).length) {
|
|
333
334
|
visitedParams.add(req.params)
|
|
334
335
|
|
|
@@ -336,7 +337,7 @@ function wrapHandleRequest (original) {
|
|
|
336
337
|
|
|
337
338
|
routerParamStartCh.publish({
|
|
338
339
|
req,
|
|
339
|
-
res,
|
|
340
|
+
res: args[1],
|
|
340
341
|
params: req?.params,
|
|
341
342
|
abortController,
|
|
342
343
|
})
|
|
@@ -344,7 +345,7 @@ function wrapHandleRequest (original) {
|
|
|
344
345
|
if (abortController.signal.aborted) return
|
|
345
346
|
}
|
|
346
347
|
|
|
347
|
-
return
|
|
348
|
+
return Reflect.apply(original, this, args)
|
|
348
349
|
}
|
|
349
350
|
}
|
|
350
351
|
|
|
@@ -358,7 +359,8 @@ addHook({
|
|
|
358
359
|
function wrapParam (original) {
|
|
359
360
|
return function wrappedProcessParams (...args) {
|
|
360
361
|
args[1] = shimmer.wrapFunction(args[1], (originalFn) => {
|
|
361
|
-
return function wrappedFn (
|
|
362
|
+
return function wrappedFn (...fnArgs) {
|
|
363
|
+
const req = fnArgs[0]
|
|
362
364
|
if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) {
|
|
363
365
|
visitedParams.add(req.params)
|
|
364
366
|
|
|
@@ -366,7 +368,7 @@ function wrapParam (original) {
|
|
|
366
368
|
|
|
367
369
|
routerParamStartCh.publish({
|
|
368
370
|
req,
|
|
369
|
-
res,
|
|
371
|
+
res: fnArgs[1],
|
|
370
372
|
params: req?.params,
|
|
371
373
|
abortController,
|
|
372
374
|
})
|
|
@@ -374,7 +376,7 @@ function wrapParam (original) {
|
|
|
374
376
|
if (abortController.signal.aborted) return
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
return
|
|
379
|
+
return Reflect.apply(originalFn, this, fnArgs)
|
|
378
380
|
}
|
|
379
381
|
})
|
|
380
382
|
|
|
@@ -57,6 +57,7 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
|
|
|
57
57
|
|
|
58
58
|
const taskToCtx = new WeakMap()
|
|
59
59
|
const taskToStatuses = new WeakMap()
|
|
60
|
+
const taskToReportedErrorCount = new WeakMap()
|
|
60
61
|
const attemptToFixTaskToStatuses = new WeakMap()
|
|
61
62
|
const originalHookFns = new WeakMap()
|
|
62
63
|
const newTasks = new WeakSet()
|
|
@@ -316,6 +317,27 @@ function recordFinalAttemptToFixExecution (task, status, providedContext) {
|
|
|
316
317
|
})
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
function disableFrameworkRetries (task) {
|
|
321
|
+
task.retry = 0
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Vitest accumulates retry and repeat errors on one task result. The first error added since
|
|
326
|
+
* the last reported attempt is the primary error for the failed attempt currently being reported.
|
|
327
|
+
*
|
|
328
|
+
* @param {object} task
|
|
329
|
+
* @param {Array<object> | undefined} errors
|
|
330
|
+
* @returns {object | undefined}
|
|
331
|
+
*/
|
|
332
|
+
function getCurrentAttemptTestError (task, errors) {
|
|
333
|
+
if (!errors?.length) return
|
|
334
|
+
|
|
335
|
+
const previousErrorCount = taskToReportedErrorCount.get(task) ?? 0
|
|
336
|
+
const testError = errors[previousErrorCount] ?? errors[0]
|
|
337
|
+
taskToReportedErrorCount.set(task, errors.length)
|
|
338
|
+
return testError
|
|
339
|
+
}
|
|
340
|
+
|
|
319
341
|
/**
|
|
320
342
|
* Wraps a function so it runs inside the current test span context.
|
|
321
343
|
* @param {object} task
|
|
@@ -801,6 +823,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
801
823
|
onDone: (isAttemptToFix) => {
|
|
802
824
|
if (isAttemptToFix) {
|
|
803
825
|
isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
|
|
826
|
+
disableFrameworkRetries(task)
|
|
804
827
|
task.repeats = testManagementAttemptToFixRetries
|
|
805
828
|
attemptToFixTasks.add(task)
|
|
806
829
|
attemptToFixTaskToStatuses.set(task, [])
|
|
@@ -831,6 +854,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
831
854
|
if (isImpacted) {
|
|
832
855
|
if (isEarlyFlakeDetectionEnabled) {
|
|
833
856
|
isRetryReasonEfd = true
|
|
857
|
+
disableFrameworkRetries(task)
|
|
834
858
|
task.repeats = numRepeats
|
|
835
859
|
}
|
|
836
860
|
modifiedTasks.add(task)
|
|
@@ -849,6 +873,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
849
873
|
if (isNew && !attemptToFixTasks.has(task)) {
|
|
850
874
|
if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
|
|
851
875
|
isRetryReasonEfd = true
|
|
876
|
+
disableFrameworkRetries(task)
|
|
852
877
|
task.repeats = numRepeats
|
|
853
878
|
}
|
|
854
879
|
newTasks.add(task)
|
|
@@ -986,7 +1011,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
986
1011
|
const promises = {}
|
|
987
1012
|
const shouldSetProbe = isDiEnabled && numAttempt === 1
|
|
988
1013
|
const ctx = taskToCtx.get(task)
|
|
989
|
-
const testError = task.result?.errors
|
|
1014
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
990
1015
|
if (ctx) {
|
|
991
1016
|
testErrorCh.publish({
|
|
992
1017
|
error: testError,
|
|
@@ -1016,7 +1041,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
1016
1041
|
const ctx = taskToCtx.get(task)
|
|
1017
1042
|
if (ctx) {
|
|
1018
1043
|
if (lastExecutionStatus === 'fail') {
|
|
1019
|
-
const testError = task.result?.errors
|
|
1044
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
1020
1045
|
testErrorCh.publish({ error: testError, ...ctx.currentStore })
|
|
1021
1046
|
} else {
|
|
1022
1047
|
testPassCh.publish({ task, ...ctx.currentStore })
|
|
@@ -1040,7 +1065,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
1040
1065
|
|
|
1041
1066
|
const ctx = taskToCtx.get(task)
|
|
1042
1067
|
if (lastExecutionStatus === 'fail') {
|
|
1043
|
-
const testError = task.result?.errors
|
|
1068
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
1044
1069
|
testErrorCh.publish({ error: testError, ...ctx.currentStore })
|
|
1045
1070
|
} else {
|
|
1046
1071
|
testPassCh.publish({ task, ...ctx.currentStore })
|
|
@@ -1389,7 +1414,7 @@ addHook({
|
|
|
1389
1414
|
|
|
1390
1415
|
if (result) {
|
|
1391
1416
|
const { state, duration, errors } = result
|
|
1392
|
-
const testError = errors
|
|
1417
|
+
const testError = getCurrentAttemptTestError(task, errors)
|
|
1393
1418
|
if (attemptToFixTasks.has(task)) {
|
|
1394
1419
|
const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
|
|
1395
1420
|
recordFinalAttemptToFixExecution(task, status, providedContext)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
4
3
|
const ClientPlugin = require('../../dd-trace/src/plugins/client')
|
|
5
4
|
const { storage } = require('../../datadog-core')
|
|
6
5
|
const { tagsFromRequest, tagsFromResponse } = require('../../dd-trace/src/payload-tagging')
|
|
@@ -113,8 +112,6 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
113
112
|
integrationName: 'aws-sdk',
|
|
114
113
|
}, ctx)
|
|
115
114
|
|
|
116
|
-
analyticsSampler.sample(span, this.config.measured)
|
|
117
|
-
|
|
118
115
|
storage('legacy').run(ctx.currentStore, () => {
|
|
119
116
|
this.requestInject(span, request)
|
|
120
117
|
})
|
|
@@ -5,6 +5,43 @@ const BaseAwsSdkPlugin = require('../base')
|
|
|
5
5
|
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
|
|
6
6
|
const { extractQueueMetadata, isEmpty } = require('../util')
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* 'detail-type'?: string,
|
|
11
|
+
* detail?: { _datadog?: Record<string, string> },
|
|
12
|
+
* Type?: string,
|
|
13
|
+
* Message?: string
|
|
14
|
+
* }} ParsedSqsBody
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the EventBridge `_datadog` text map from a parsed SQS body — for both
|
|
19
|
+
* EventBridge -> SQS (`body.detail._datadog`) and EventBridge -> SNS -> SQS (the
|
|
20
|
+
* envelope is the SNS `Notification`'s stringified `Message`). Keyed off
|
|
21
|
+
* `detail-type`, the marker AWS sets on every PutEvents delivery. Relies on the
|
|
22
|
+
* default SQS-target shape; a target InputTransformer can drop `detail`.
|
|
23
|
+
*
|
|
24
|
+
* @param {ParsedSqsBody} [parsedBody]
|
|
25
|
+
* @returns {Record<string, string> | undefined}
|
|
26
|
+
*/
|
|
27
|
+
function getEventBridgeContext (parsedBody) {
|
|
28
|
+
let envelope
|
|
29
|
+
if (parsedBody?.['detail-type'] !== undefined) {
|
|
30
|
+
envelope = parsedBody // EventBridge -> SQS
|
|
31
|
+
} else if (parsedBody?.Type === 'Notification' && typeof parsedBody.Message === 'string') {
|
|
32
|
+
// EventBridge -> SNS -> SQS
|
|
33
|
+
try {
|
|
34
|
+
const innerEnvelope = JSON.parse(parsedBody.Message)
|
|
35
|
+
if (innerEnvelope?.['detail-type'] !== undefined) {
|
|
36
|
+
envelope = innerEnvelope
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// SNS `Message` not JSON
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return envelope?.detail?._datadog
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
class Sqs extends BaseAwsSdkPlugin {
|
|
9
46
|
static id = 'sqs'
|
|
10
47
|
static peerServicePrecursors = ['queuename']
|
|
@@ -149,17 +186,28 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
149
186
|
}
|
|
150
187
|
}
|
|
151
188
|
|
|
152
|
-
|
|
153
|
-
|
|
189
|
+
// Check MessageAttributes first (common direct-SQS/SNS path): avoids parsing
|
|
190
|
+
// the body (e.g. a large SNS `Message`) just to rule out an EventBridge
|
|
191
|
+
// envelope. Precedence matches responseExtractDSMContext.
|
|
192
|
+
const datadogAttribute = message.MessageAttributes?._datadog
|
|
193
|
+
if (datadogAttribute) {
|
|
194
|
+
const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
|
|
195
|
+
if (parsedAttributes) {
|
|
196
|
+
return {
|
|
197
|
+
datadogContext: this.tracer.extract('text_map', parsedAttributes),
|
|
198
|
+
parsedAttributes,
|
|
199
|
+
parsedBody,
|
|
200
|
+
bodyChecked: true,
|
|
201
|
+
}
|
|
202
|
+
}
|
|
154
203
|
}
|
|
155
204
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (parsedAttributes) {
|
|
205
|
+
// Then the EventBridge envelope (optionally via SNS); see getEventBridgeContext.
|
|
206
|
+
const eventBridgeContext = getEventBridgeContext(parsedBody)
|
|
207
|
+
if (eventBridgeContext) {
|
|
160
208
|
return {
|
|
161
|
-
datadogContext: this.tracer.extract('text_map',
|
|
162
|
-
parsedAttributes,
|
|
209
|
+
datadogContext: this.tracer.extract('text_map', eventBridgeContext),
|
|
210
|
+
parsedAttributes: eventBridgeContext,
|
|
163
211
|
parsedBody,
|
|
164
212
|
bodyChecked: true,
|
|
165
213
|
}
|
|
@@ -213,15 +261,18 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
213
261
|
if (body?.Type === 'Notification') {
|
|
214
262
|
message = body
|
|
215
263
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
264
|
+
// MessageAttributes for direct SQS/SNS; else the EventBridge envelope.
|
|
265
|
+
parsedAttributes = message.MessageAttributes?._datadog
|
|
266
|
+
? this.parseDatadogAttributes(message.MessageAttributes._datadog)
|
|
267
|
+
: getEventBridgeContext(body)
|
|
219
268
|
}
|
|
220
269
|
const payloadSize = getHeadersSize({
|
|
221
270
|
Body: message.Body,
|
|
222
271
|
MessageAttributes: message.MessageAttributes,
|
|
223
272
|
})
|
|
224
273
|
if (parsedAttributes) {
|
|
274
|
+
// Inert for EventBridge until its producer emits a pathway (separate
|
|
275
|
+
// change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
|
|
225
276
|
this.tracer.decodeDataStreamsContext(parsedAttributes)
|
|
226
277
|
}
|
|
227
278
|
this.tracer
|
|
@@ -80,6 +80,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
80
80
|
isTestManagementTestsEnabled,
|
|
81
81
|
isParallel,
|
|
82
82
|
}) => {
|
|
83
|
+
this._exportPendingWorkerTraces()
|
|
83
84
|
const {
|
|
84
85
|
isSuitesSkippingEnabled,
|
|
85
86
|
isCodeCoverageEnabled,
|
|
@@ -186,6 +187,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
186
187
|
integrationName: this.constructor.id,
|
|
187
188
|
})
|
|
188
189
|
this._testSuiteSpansByTestSuite.set(testSuitePath, testSuiteSpan)
|
|
190
|
+
this._exportPendingWorkerTracesForTestSuite(testSuitePath)
|
|
189
191
|
|
|
190
192
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
191
193
|
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
@@ -136,6 +136,7 @@ function getRetriedTests (test, numRetries, tags) {
|
|
|
136
136
|
// TODO: signal in framework logs that this is a retry.
|
|
137
137
|
// TODO: Change it so these tests are allowed to fail.
|
|
138
138
|
const clonedTest = test.clone()
|
|
139
|
+
disableFrameworkRetries(clonedTest)
|
|
139
140
|
if (tags.includes('_ddIsEfdRetry')) {
|
|
140
141
|
clonedTest._ddEfdRetryIndex = retryIndex + 1
|
|
141
142
|
}
|
|
@@ -149,6 +150,19 @@ function getRetriedTests (test, numRetries, tags) {
|
|
|
149
150
|
return retriedTests
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
function disableFrameworkRetries (test) {
|
|
154
|
+
test._retries = 0
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function shouldDisableFrameworkRetries (test) {
|
|
158
|
+
return test && (
|
|
159
|
+
test._ddIsAttemptToFix ||
|
|
160
|
+
(isTestIsolationEnabled &&
|
|
161
|
+
isEarlyFlakeDetectionEnabled &&
|
|
162
|
+
(test._ddIsNew || test._ddIsModified))
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
const oldRunTests = Cypress.mocha.getRunner().runTests
|
|
153
167
|
Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
154
168
|
if (!isKnownTestsEnabled && !isTestManagementEnabled && !isImpactedTestsEnabled) {
|
|
@@ -188,9 +202,11 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
188
202
|
let retryMessage = ''
|
|
189
203
|
if (isAtemptToFix) {
|
|
190
204
|
test._ddIsAttemptToFix = true
|
|
205
|
+
disableFrameworkRetries(test)
|
|
191
206
|
retryMessage = 'because it is an attempt to fix'
|
|
192
207
|
retriedTests = getRetriedTests(test, testManagementAttemptToFixRetries, ['_ddIsAttemptToFix'])
|
|
193
208
|
} else if (isModified && isEarlyFlakeDetectionEnabled) {
|
|
209
|
+
disableFrameworkRetries(test)
|
|
194
210
|
retryMessage = 'to detect flakes because it is modified'
|
|
195
211
|
retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, [
|
|
196
212
|
'_ddIsModified',
|
|
@@ -198,6 +214,7 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
198
214
|
isKnownTestsEnabled && isNewTest(test) && '_ddIsNew',
|
|
199
215
|
])
|
|
200
216
|
} else if (isNew && isEarlyFlakeDetectionEnabled) {
|
|
217
|
+
disableFrameworkRetries(test)
|
|
201
218
|
retryMessage = 'to detect flakes because it is new'
|
|
202
219
|
retriedTests = getRetriedTests(test, earlyFlakeDetectionNumRetries, ['_ddIsNew', '_ddIsEfdRetry'])
|
|
203
220
|
}
|
|
@@ -214,8 +231,21 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
|
214
231
|
return oldRunTests.apply(this, [suite, fn])
|
|
215
232
|
}
|
|
216
233
|
|
|
234
|
+
Cypress.on('test:before:run', (attributes, test) => {
|
|
235
|
+
if (shouldDisableFrameworkRetries(test)) {
|
|
236
|
+
disableFrameworkRetries(test)
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
Cypress.on('test:before:run:async', (attributes, test) => {
|
|
241
|
+
if (shouldDisableFrameworkRetries(test)) {
|
|
242
|
+
disableFrameworkRetries(test)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
217
246
|
beforeEach(function () {
|
|
218
|
-
const
|
|
247
|
+
const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
|
|
248
|
+
const testName = currentTest.fullTitle()
|
|
219
249
|
|
|
220
250
|
const retryMessage = retryReasonsByTestName.get(testName)
|
|
221
251
|
if (retryMessage) {
|
|
@@ -5,7 +5,6 @@ const { URL } = require('url')
|
|
|
5
5
|
const ClientPlugin = require('../../dd-trace/src/plugins/client')
|
|
6
6
|
const { storage } = require('../../datadog-core')
|
|
7
7
|
const tags = require('../../../ext/tags')
|
|
8
|
-
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
9
8
|
const formats = require('../../../ext/formats')
|
|
10
9
|
const HTTP_HEADERS = formats.HTTP_HEADERS
|
|
11
10
|
const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
|
|
@@ -69,8 +68,6 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
69
68
|
this.tracer.inject(span, HTTP_HEADERS, options.headers)
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
analyticsSampler.sample(span, this.config.measured)
|
|
73
|
-
|
|
74
71
|
message.span = span
|
|
75
72
|
message.parentStore = store
|
|
76
73
|
message.currentStore = { ...store, span }
|
|
@@ -34,6 +34,11 @@ class HttpServerPlugin extends ServerPlugin {
|
|
|
34
34
|
if (this.#startConfig === undefined) {
|
|
35
35
|
this.#refreshStartCache()
|
|
36
36
|
}
|
|
37
|
+
// A fresh request has no span yet; web.startSpan creates and enters one.
|
|
38
|
+
// A reused context (HTTP/2 stream, serverless re-entry) returns the
|
|
39
|
+
// existing span without entering, so the explicit enter below is the only
|
|
40
|
+
// one in that case.
|
|
41
|
+
const spanCreated = !web.getContext(req)?.span
|
|
37
42
|
const span = web.startSpan(
|
|
38
43
|
this.tracer,
|
|
39
44
|
this.#startConfig,
|
|
@@ -57,7 +62,12 @@ class HttpServerPlugin extends ServerPlugin {
|
|
|
57
62
|
store = withRequest(store, req)
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
// Skip the re-enter when web.startSpan already entered { ...store, span }
|
|
66
|
+
// and AppSec did not rebuild the store with req: the bind would be
|
|
67
|
+
// identical to the one startSpan just made.
|
|
68
|
+
if (!spanCreated || appsecActive) {
|
|
69
|
+
this.enter(span, store)
|
|
70
|
+
}
|
|
61
71
|
|
|
62
72
|
if (!context.instrumented) {
|
|
63
73
|
context.res.writeHead = web.wrapWriteHead(context)
|
|
@@ -150,6 +150,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
150
150
|
ctx.parentStore = store
|
|
151
151
|
ctx.currentStore = { ...store, testSuiteSpan }
|
|
152
152
|
this._testSuiteSpansByTestSuite.set(testSuite, testSuiteSpan)
|
|
153
|
+
this._exportPendingWorkerTracesForTestSuite(testSuite)
|
|
153
154
|
})
|
|
154
155
|
|
|
155
156
|
this.addSub('ci:mocha:test-suite:finish', ({ testSuiteSpan, status }) => {
|
|
@@ -365,6 +366,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
365
366
|
isTestManagementEnabled,
|
|
366
367
|
isParallel,
|
|
367
368
|
}) => {
|
|
369
|
+
this._exportPendingWorkerTraces()
|
|
368
370
|
if (this.testSessionSpan) {
|
|
369
371
|
const {
|
|
370
372
|
isSuitesSkippingEnabled,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
3
4
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
4
5
|
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
5
6
|
|
|
@@ -8,6 +9,15 @@ class PGPlugin extends DatabasePlugin {
|
|
|
8
9
|
static operation = 'query'
|
|
9
10
|
static system = 'postgres'
|
|
10
11
|
|
|
12
|
+
constructor () {
|
|
13
|
+
super(...arguments)
|
|
14
|
+
|
|
15
|
+
this.addSub('apm:pg:pool:connect:start', ctx => {
|
|
16
|
+
ctx.parentStore = storage('legacy').getStore()
|
|
17
|
+
})
|
|
18
|
+
this.addBind('apm:pg:pool:connect:finish', ctx => ctx.parentStore)
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
bindStart (ctx) {
|
|
12
22
|
const { params = {}, query, originalText, processId, stream } = ctx
|
|
13
23
|
const service = this.serviceName({ pluginConfig: this.config, params })
|