dd-trace 5.108.0 → 5.109.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 +22 -1
- package/package.json +2 -1
- package/packages/datadog-instrumentations/src/ai.js +43 -48
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
- package/packages/datadog-instrumentations/src/electron.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/http/client.js +12 -2
- package/packages/datadog-instrumentations/src/ioredis.js +0 -1
- package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
- package/packages/datadog-instrumentations/src/next.js +34 -0
- package/packages/datadog-instrumentations/src/openai.js +77 -18
- package/packages/datadog-instrumentations/src/redis.js +0 -1
- package/packages/datadog-instrumentations/src/vitest.js +60 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -7
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +44 -27
- package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
- package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
- package/packages/datadog-plugin-fs/src/index.js +1 -0
- package/packages/datadog-plugin-redis/src/index.js +1 -2
- package/packages/datadog-plugin-vitest/src/index.js +4 -1
- package/packages/dd-trace/src/aiguard/channels.js +0 -1
- package/packages/dd-trace/src/aiguard/index.js +11 -49
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
- package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
- package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
- package/packages/dd-trace/src/config/supported-configurations.json +24 -2
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -0
- package/packages/dd-trace/src/dogstatsd.js +15 -8
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
- package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +8 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +27 -2
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
|
@@ -3,11 +3,52 @@
|
|
|
3
3
|
const dc = require('dc-polyfill')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
5
|
const { addHook } = require('./helpers/instrument')
|
|
6
|
-
const aiGuard = require('./helpers/openai-ai-guard')
|
|
7
6
|
|
|
8
7
|
const ch = dc.tracingChannel('apm:openai:request')
|
|
9
8
|
const onStreamedChunkCh = dc.channel('apm:openai:request:chunk')
|
|
10
9
|
|
|
10
|
+
// Provider lifecycle channels. Payloads stay OpenAI-native:
|
|
11
|
+
// before { args, parentSpan, abortController, pending }
|
|
12
|
+
// after { args, body, parentSpan, abortController, pending }
|
|
13
|
+
const chatCompletionsBeforeChannel = dc.channel('dd-trace:openai:chat.completions:before')
|
|
14
|
+
const chatCompletionsAfterChannel = dc.channel('dd-trace:openai:chat.completions:after')
|
|
15
|
+
const responsesBeforeChannel = dc.channel('dd-trace:openai:responses:before')
|
|
16
|
+
const responsesAfterChannel = dc.channel('dd-trace:openai:responses:after')
|
|
17
|
+
|
|
18
|
+
const LIFECYCLE_CHANNELS = {
|
|
19
|
+
'chat.completions': {
|
|
20
|
+
before: chatCompletionsBeforeChannel,
|
|
21
|
+
after: chatCompletionsAfterChannel,
|
|
22
|
+
},
|
|
23
|
+
responses: {
|
|
24
|
+
before: responsesBeforeChannel,
|
|
25
|
+
after: responsesAfterChannel,
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Publishes a provider-native lifecycle payload to a cancelable lifecycle channel.
|
|
31
|
+
*
|
|
32
|
+
* Subscribers push async work into `pending` synchronously during publication and
|
|
33
|
+
* abort `abortController` with an error before the pushed promise resolves to block.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} channel
|
|
36
|
+
* @param {object} payload
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
function publishLifecycle (channel, payload) {
|
|
40
|
+
const abortController = new AbortController()
|
|
41
|
+
const ctx = { ...payload, abortController, pending: [] }
|
|
42
|
+
|
|
43
|
+
channel.publish(ctx)
|
|
44
|
+
|
|
45
|
+
return Promise.all(ctx.pending).then(() => {
|
|
46
|
+
if (abortController.signal.aborted) {
|
|
47
|
+
throw abortController.signal.reason
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
const V4_PACKAGE_SHIMS = [
|
|
12
53
|
{
|
|
13
54
|
file: 'resources/chat/completions',
|
|
@@ -217,17 +258,15 @@ for (const extension of extensions) {
|
|
|
217
258
|
|
|
218
259
|
for (const methodName of methods) {
|
|
219
260
|
shimmer.wrap(targetPrototype, methodName, methodFn => function (...args) {
|
|
220
|
-
if (!ch.start.hasSubscribers && !aiGuard.hasSubscribers()) {
|
|
221
|
-
return methodFn.apply(this, args)
|
|
222
|
-
}
|
|
223
261
|
// The OpenAI library lets you set `stream: true` on the options arg to any method
|
|
224
262
|
// However, we only want to handle streamed responses in specific cases
|
|
225
263
|
// chat.completions and completions
|
|
226
264
|
const stream = streamedResponse && getOption(args, 'stream', false)
|
|
227
265
|
|
|
228
|
-
const
|
|
266
|
+
const channels = stream ? null : LIFECYCLE_CHANNELS[baseResource]
|
|
267
|
+
const hasLifecycle = !!channels && (channels.before.hasSubscribers || channels.after.hasSubscribers)
|
|
229
268
|
|
|
230
|
-
if (!ch.start.hasSubscribers && !
|
|
269
|
+
if (!ch.start.hasSubscribers && !hasLifecycle) {
|
|
231
270
|
return methodFn.apply(this, args)
|
|
232
271
|
}
|
|
233
272
|
|
|
@@ -240,12 +279,22 @@ for (const extension of extensions) {
|
|
|
240
279
|
}
|
|
241
280
|
|
|
242
281
|
return ch.start.runStores(ctx, () => {
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
282
|
+
// Capture the parent span explicitly: the _thenUnwrap/parse path decouples
|
|
283
|
+
// the lazy evaluation from the active scope at call time.
|
|
284
|
+
const parentSpan = hasLifecycle ? ctx.currentStore?.span : undefined
|
|
246
285
|
|
|
247
286
|
const apiProm = methodFn.apply(this, args)
|
|
248
287
|
|
|
288
|
+
const beforeChannel = hasLifecycle && channels.before.hasSubscribers ? channels.before : null
|
|
289
|
+
const afterChannel = hasLifecycle && channels.after.hasSubscribers ? channels.after : null
|
|
290
|
+
let beforeVerdict
|
|
291
|
+
const getBeforeVerdict = beforeChannel
|
|
292
|
+
? function getBeforeVerdict () {
|
|
293
|
+
beforeVerdict ??= publishLifecycle(beforeChannel, { args, parentSpan })
|
|
294
|
+
return beforeVerdict
|
|
295
|
+
}
|
|
296
|
+
: null
|
|
297
|
+
|
|
249
298
|
if (baseResource === 'chat.completions' && typeof apiProm._thenUnwrap === 'function') {
|
|
250
299
|
// this should only ever be invoked from a client.beta.chat.completions.parse call
|
|
251
300
|
shimmer.wrap(apiProm, '_thenUnwrap', origApiPromThenUnwrap => function (...args) {
|
|
@@ -259,7 +308,9 @@ for (const extension of extensions) {
|
|
|
259
308
|
const parsedPromise = origApiPromParse.apply(this, args)
|
|
260
309
|
.then(body => Promise.all([this.responsePromise, body]))
|
|
261
310
|
|
|
262
|
-
return handleUnwrappedAPIPromise(
|
|
311
|
+
return handleUnwrappedAPIPromise(
|
|
312
|
+
parsedPromise, ctx, stream, getBeforeVerdict, afterChannel, parentSpan
|
|
313
|
+
)
|
|
263
314
|
})
|
|
264
315
|
|
|
265
316
|
return unwrappedPromise
|
|
@@ -272,10 +323,16 @@ for (const extension of extensions) {
|
|
|
272
323
|
const parsedPromise = origApiPromParse.apply(this, args)
|
|
273
324
|
.then(body => Promise.all([this.responsePromise, body]))
|
|
274
325
|
|
|
275
|
-
return handleUnwrappedAPIPromise(parsedPromise, ctx, stream,
|
|
326
|
+
return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, getBeforeVerdict, afterChannel, parentSpan)
|
|
276
327
|
})
|
|
277
328
|
|
|
278
|
-
|
|
329
|
+
// Gate `.asResponse()` callers on the before verdict so raw-response paths still block.
|
|
330
|
+
if (beforeChannel && typeof apiProm.asResponse === 'function') {
|
|
331
|
+
shimmer.wrap(apiProm, 'asResponse', origAsResponse => function (...args) {
|
|
332
|
+
const responsePromise = origAsResponse.apply(this, args)
|
|
333
|
+
return Promise.all([getBeforeVerdict(), responsePromise]).then(([, response]) => response)
|
|
334
|
+
})
|
|
335
|
+
}
|
|
279
336
|
|
|
280
337
|
ch.end.publish(ctx)
|
|
281
338
|
|
|
@@ -288,10 +345,12 @@ for (const extension of extensions) {
|
|
|
288
345
|
}
|
|
289
346
|
}
|
|
290
347
|
|
|
291
|
-
function handleUnwrappedAPIPromise (apiProm, ctx, stream,
|
|
292
|
-
const
|
|
348
|
+
function handleUnwrappedAPIPromise (apiProm, ctx, stream, getBeforeVerdict, afterChannel, parentSpan) {
|
|
349
|
+
const gatedApiProm = getBeforeVerdict
|
|
350
|
+
? Promise.all([getBeforeVerdict(), apiProm]).then(([, result]) => result)
|
|
351
|
+
: apiProm
|
|
293
352
|
|
|
294
|
-
return
|
|
353
|
+
return gatedApiProm
|
|
295
354
|
.then(([{ response, options }, body]) => {
|
|
296
355
|
if (stream) {
|
|
297
356
|
if (body.iterator) {
|
|
@@ -313,14 +372,14 @@ function handleUnwrappedAPIPromise (apiProm, ctx, stream, guard) {
|
|
|
313
372
|
},
|
|
314
373
|
}
|
|
315
374
|
|
|
316
|
-
if (!
|
|
375
|
+
if (!afterChannel) {
|
|
317
376
|
finish(ctx, responseData)
|
|
318
377
|
return body
|
|
319
378
|
}
|
|
320
379
|
|
|
321
380
|
// Finish after evaluation so a block propagates the error to openai.request
|
|
322
|
-
// and the span wraps its
|
|
323
|
-
return
|
|
381
|
+
// and the span wraps its child instead of closing before it.
|
|
382
|
+
return publishLifecycle(afterChannel, { args: ctx.args, body, parentSpan }).then(() => {
|
|
324
383
|
finish(ctx, responseData)
|
|
325
384
|
return body
|
|
326
385
|
})
|
|
@@ -143,6 +143,8 @@ function getProvidedContext () {
|
|
|
143
143
|
_ddTestSessionId: testSessionId,
|
|
144
144
|
_ddTestModuleId: testModuleId,
|
|
145
145
|
_ddTestCommand: testCommand,
|
|
146
|
+
_ddRepositoryRoot: repositoryRoot,
|
|
147
|
+
_ddCodeOwnersEntries: codeOwnersEntries,
|
|
146
148
|
} = globalThis.__vitest_worker__.providedContext
|
|
147
149
|
|
|
148
150
|
return {
|
|
@@ -162,6 +164,8 @@ function getProvidedContext () {
|
|
|
162
164
|
testSessionId,
|
|
163
165
|
testModuleId,
|
|
164
166
|
testCommand,
|
|
167
|
+
repositoryRoot,
|
|
168
|
+
codeOwnersEntries,
|
|
165
169
|
}
|
|
166
170
|
} catch {
|
|
167
171
|
log.error('Vitest workers could not parse provided context, so some features will not work.')
|
|
@@ -182,6 +186,8 @@ function getProvidedContext () {
|
|
|
182
186
|
testSessionId: undefined,
|
|
183
187
|
testModuleId: undefined,
|
|
184
188
|
testCommand: undefined,
|
|
189
|
+
repositoryRoot: undefined,
|
|
190
|
+
codeOwnersEntries: undefined,
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
}
|
|
@@ -472,7 +478,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications) {
|
|
|
472
478
|
}
|
|
473
479
|
|
|
474
480
|
if (testSessionConfigurationCh.hasSubscribers) {
|
|
475
|
-
const { testSessionId, testModuleId, testCommand } = await getChannelPromise(
|
|
481
|
+
const { testSessionId, testModuleId, testCommand, repositoryRoot, codeOwnersEntries } = await getChannelPromise(
|
|
476
482
|
testSessionConfigurationCh,
|
|
477
483
|
frameworkVersion
|
|
478
484
|
)
|
|
@@ -480,6 +486,8 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications) {
|
|
|
480
486
|
_ddTestSessionId: testSessionId,
|
|
481
487
|
_ddTestModuleId: testModuleId,
|
|
482
488
|
_ddTestCommand: testCommand,
|
|
489
|
+
_ddRepositoryRoot: repositoryRoot,
|
|
490
|
+
_ddCodeOwnersEntries: codeOwnersEntries,
|
|
483
491
|
}, 'Could not send test session configuration to workers.')
|
|
484
492
|
}
|
|
485
493
|
|
|
@@ -653,15 +661,64 @@ function getCliOrStartVitestWrapper (frameworkVersion) {
|
|
|
653
661
|
}
|
|
654
662
|
}
|
|
655
663
|
|
|
664
|
+
function isForkPool (pool) {
|
|
665
|
+
return pool === 'forks' || pool === 'vmForks'
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function isThreadPool (pool) {
|
|
669
|
+
return pool === 'threads' || pool === 'vmThreads'
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function getTestSpecificationPool (testSpecification) {
|
|
673
|
+
const project = Array.isArray(testSpecification) ? testSpecification[0] : testSpecification?.project
|
|
674
|
+
return project?.config?.pool || project?.serializedConfig?.pool || project?.pool || testSpecification?.pool
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function hasForkPoolTestSpecification (testSpecifications) {
|
|
678
|
+
if (!Array.isArray(testSpecifications)) {
|
|
679
|
+
return false
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
for (const testSpecification of testSpecifications) {
|
|
683
|
+
if (isForkPool(getTestSpecificationPool(testSpecification))) {
|
|
684
|
+
return true
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return false
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function shouldMarkVitestWorkerEnv (pool, testSpecifications) {
|
|
692
|
+
return isForkPool(pool) || hasForkPoolTestSpecification(testSpecifications) ||
|
|
693
|
+
(!testSpecifications && !isThreadPool(pool))
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function markVitestWorkerEnv (ctx, testSpecifications) {
|
|
697
|
+
const config = ctx?.config
|
|
698
|
+
if (!config || !shouldMarkVitestWorkerEnv(config.pool, testSpecifications)) {
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
config.env = config.env || {}
|
|
702
|
+
config.env.DD_VITEST_WORKER = '1'
|
|
703
|
+
}
|
|
704
|
+
|
|
656
705
|
function wrapVitestRunFiles (Vitest, frameworkVersion) {
|
|
657
706
|
if (!Vitest?.prototype?.runFiles) {
|
|
658
707
|
return
|
|
659
708
|
}
|
|
660
709
|
|
|
661
710
|
shimmer.wrap(Vitest.prototype, 'runFiles', runFiles => async function (testSpecifications) {
|
|
711
|
+
markVitestWorkerEnv(this, testSpecifications)
|
|
662
712
|
await ensureMainProcessSetup(this, frameworkVersion, testSpecifications)
|
|
663
713
|
return runFiles.apply(this, arguments)
|
|
664
714
|
})
|
|
715
|
+
|
|
716
|
+
if (Vitest.prototype.collectTests) {
|
|
717
|
+
shimmer.wrap(Vitest.prototype, 'collectTests', collectTests => function () {
|
|
718
|
+
markVitestWorkerEnv(this)
|
|
719
|
+
return collectTests.apply(this, arguments)
|
|
720
|
+
})
|
|
721
|
+
}
|
|
665
722
|
}
|
|
666
723
|
|
|
667
724
|
function getCreateCliWrapper (vitestPackage, frameworkVersion) {
|
|
@@ -1393,6 +1450,8 @@ addHook({
|
|
|
1393
1450
|
testSessionId: providedContext.testSessionId,
|
|
1394
1451
|
testModuleId: providedContext.testModuleId,
|
|
1395
1452
|
testCommand: providedContext.testCommand,
|
|
1453
|
+
repositoryRoot: providedContext.repositoryRoot,
|
|
1454
|
+
codeOwnersEntries: providedContext.codeOwnersEntries,
|
|
1396
1455
|
}
|
|
1397
1456
|
testSuiteStartCh.runStores(testSuiteCtx, () => {})
|
|
1398
1457
|
const startTestsResponse = await startTests.apply(this, arguments)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
|
|
5
|
+
// On retries the SDK suspends execution without firing error/asyncEnd; finish the span here.
|
|
6
|
+
class AwsDurableExecutionSdkJsCheckpointPlugin extends TracingPlugin {
|
|
7
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
8
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:CheckpointManager_checkpoint'
|
|
9
|
+
|
|
10
|
+
start (ctx) {
|
|
11
|
+
const data = ctx.arguments?.[1]
|
|
12
|
+
if (data?.Action !== 'RETRY' || !data.Error) return
|
|
13
|
+
|
|
14
|
+
const span = this.activeSpan
|
|
15
|
+
if (!span || span.context().getTag('error')) return
|
|
16
|
+
|
|
17
|
+
const { ErrorMessage, ErrorType, StackTrace } = data.Error
|
|
18
|
+
span.setTag('error', 1)
|
|
19
|
+
if (ErrorMessage) span.setTag('error.message', ErrorMessage)
|
|
20
|
+
if (ErrorType) span.setTag('error.type', ErrorType)
|
|
21
|
+
if (Array.isArray(StackTrace)) span.setTag('error.stack', StackTrace.join('\n'))
|
|
22
|
+
|
|
23
|
+
ctx.retryStepSpan = span
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
asyncEnd (ctx) {
|
|
27
|
+
ctx.retryStepSpan?.finish()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = AwsDurableExecutionSdkJsCheckpointPlugin
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ClientPlugin = require('../../dd-trace/src/plugins/client')
|
|
4
|
+
const { addOpMeta, unwrapDurableError } = require('./util')
|
|
5
|
+
|
|
6
|
+
class AwsDurableExecutionSdkJsClientPlugin extends ClientPlugin {
|
|
7
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
8
|
+
static type = 'serverless'
|
|
9
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_invoke'
|
|
10
|
+
static settleChannel = 'apm:aws-durable-execution-sdk-js:invoke:settle'
|
|
11
|
+
|
|
12
|
+
constructor (...args) {
|
|
13
|
+
super(...args)
|
|
14
|
+
this.addSub(this.constructor.settleChannel, ctx => this.settle(ctx))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// invoke has two overloads: invoke(name, funcId, ...) and invoke(funcId, ...).
|
|
18
|
+
// They're distinguished by whether args[1] is a string (named form) or not.
|
|
19
|
+
bindStart (ctx) {
|
|
20
|
+
const args = ctx.arguments || []
|
|
21
|
+
const isNamed = typeof args[1] === 'string'
|
|
22
|
+
const operationName = isNamed ? args[0] : undefined
|
|
23
|
+
const functionName = isNamed ? args[1] : args[0]
|
|
24
|
+
|
|
25
|
+
const meta = {}
|
|
26
|
+
if (functionName) {
|
|
27
|
+
meta['aws.durable.invoke.function_name'] = functionName
|
|
28
|
+
}
|
|
29
|
+
if (operationName) {
|
|
30
|
+
meta['aws.durable.operation_name'] = operationName
|
|
31
|
+
}
|
|
32
|
+
addOpMeta(meta, ctx.self)
|
|
33
|
+
|
|
34
|
+
this.startSpan(this.operationName(), {
|
|
35
|
+
resource: operationName,
|
|
36
|
+
kind: this.constructor.kind,
|
|
37
|
+
meta,
|
|
38
|
+
}, ctx)
|
|
39
|
+
|
|
40
|
+
return ctx.currentStore
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
settle (ctx) {
|
|
44
|
+
if (ctx.error !== undefined) {
|
|
45
|
+
ctx.currentStore?.span?.setTag('error', unwrapDurableError(ctx))
|
|
46
|
+
}
|
|
47
|
+
this.finish(ctx)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
error (ctxOrError) {
|
|
51
|
+
this.settle(ctxOrError)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = AwsDurableExecutionSdkJsClientPlugin
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
4
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
5
|
+
const { addOpMeta, unwrapDurableError } = require('./util')
|
|
6
|
+
|
|
7
|
+
// Span names whose direct children must keep the default resource.
|
|
8
|
+
// These can have very high cardinality which is undesireable in the resource.
|
|
9
|
+
const HIGH_CARDINALITY_PARENT_SPAN_NAMES = new Set([
|
|
10
|
+
'aws.durable.map',
|
|
11
|
+
'aws.durable.parallel',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
// The SDK emits these subTypes as internal scaffolding around map/parallel iterations
|
|
15
|
+
// and waitForCallback; not user-visible operations.
|
|
16
|
+
const SUPPRESSED_CHILD_CONTEXT_SUBTYPES = new Set([
|
|
17
|
+
'Map',
|
|
18
|
+
'Parallel',
|
|
19
|
+
'MapIteration',
|
|
20
|
+
'ParallelBranch',
|
|
21
|
+
'WaitForCallback',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
class BaseContextPlugin extends TracingPlugin {
|
|
25
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
26
|
+
static type = 'serverless'
|
|
27
|
+
static kind = 'internal'
|
|
28
|
+
|
|
29
|
+
constructor (...args) {
|
|
30
|
+
super(...args)
|
|
31
|
+
this.addSub(this.constructor.settleChannel, ctx => this.settle(ctx))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
bindStart (ctx) {
|
|
35
|
+
const spanName = this.constructor.spanName
|
|
36
|
+
const parentName = this.activeSpan?.context()._name
|
|
37
|
+
const operationName = this.getOperationName(ctx)
|
|
38
|
+
const resource = HIGH_CARDINALITY_PARENT_SPAN_NAMES.has(parentName) ? undefined : operationName
|
|
39
|
+
|
|
40
|
+
const meta = {}
|
|
41
|
+
if (operationName) {
|
|
42
|
+
meta['aws.durable.operation_name'] = operationName
|
|
43
|
+
}
|
|
44
|
+
addOpMeta(meta, ctx.self)
|
|
45
|
+
|
|
46
|
+
this.startSpan(spanName, {
|
|
47
|
+
resource,
|
|
48
|
+
kind: this.constructor.kind,
|
|
49
|
+
meta,
|
|
50
|
+
}, ctx)
|
|
51
|
+
|
|
52
|
+
return ctx.currentStore
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// All context methods have two overloads: method(name, …) and method(…); args[0] is the name in the first form.
|
|
56
|
+
getOperationName (ctx) {
|
|
57
|
+
const args = ctx.arguments || []
|
|
58
|
+
return typeof args[0] === 'string' ? args[0] : undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
settle (ctx) {
|
|
62
|
+
if (ctx.suppressed) return
|
|
63
|
+
if (ctx.error !== undefined) {
|
|
64
|
+
ctx.currentStore?.span?.setTag('error', unwrapDurableError(ctx))
|
|
65
|
+
}
|
|
66
|
+
this.finish(ctx)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
error (ctxOrError) {
|
|
70
|
+
this.settle(ctxOrError)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeContextPlugin (method, spanName) {
|
|
75
|
+
return class extends BaseContextPlugin {
|
|
76
|
+
static prefix = `tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_${method}`
|
|
77
|
+
static settleChannel = `apm:aws-durable-execution-sdk-js:${method}:settle`
|
|
78
|
+
static spanName = spanName
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class RunInChildContextPlugin extends BaseContextPlugin {
|
|
83
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_runInChildContext'
|
|
84
|
+
static settleChannel = 'apm:aws-durable-execution-sdk-js:runInChildContext:settle'
|
|
85
|
+
static spanName = 'aws.durable.child_context'
|
|
86
|
+
|
|
87
|
+
bindStart (ctx) {
|
|
88
|
+
if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubType(ctx))) {
|
|
89
|
+
// Pass the active store through unchanged so any nested spans
|
|
90
|
+
// remain parented to the surrounding map/parallel span
|
|
91
|
+
ctx.suppressed = true
|
|
92
|
+
return storage('legacy').getStore()
|
|
93
|
+
}
|
|
94
|
+
return super.bindStart(ctx)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// runInChildContext has two overloads: `(name, fn, options)` and `(fn, options)`.
|
|
99
|
+
function getRunInChildContextSubType (ctx) {
|
|
100
|
+
const args = ctx.arguments || []
|
|
101
|
+
const opts = typeof args[0] === 'function' ? args[1] : args[2]
|
|
102
|
+
return opts?.subType
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
step: makeContextPlugin('step', 'aws.durable.step'),
|
|
107
|
+
wait: makeContextPlugin('wait', 'aws.durable.wait'),
|
|
108
|
+
waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition'),
|
|
109
|
+
waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback'),
|
|
110
|
+
createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback'),
|
|
111
|
+
map: makeContextPlugin('map', 'aws.durable.map'),
|
|
112
|
+
parallel: makeContextPlugin('parallel', 'aws.durable.parallel'),
|
|
113
|
+
runInChildContext: RunInChildContextPlugin,
|
|
114
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
4
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
5
|
+
const { saveTraceContextCheckpointIfUpdated } = require('./trace-checkpoint')
|
|
6
|
+
|
|
7
|
+
// Termination reasons that indicate the execution is suspending rather than exiting permanently.
|
|
8
|
+
// Sourced from (`@aws/durable-execution-sdk-js`'s termination-manager/types.ts).
|
|
9
|
+
const PENDING_TERMINATION_REASONS = new Set([
|
|
10
|
+
'OPERATION_TERMINATED',
|
|
11
|
+
'RETRY_SCHEDULED',
|
|
12
|
+
'RETRY_INTERRUPTED_STEP',
|
|
13
|
+
'WAIT_SCHEDULED',
|
|
14
|
+
'CALLBACK_PENDING',
|
|
15
|
+
'CUSTOM',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const DEFAULT_TERMINATION_REASON = 'OPERATION_TERMINATED'
|
|
19
|
+
|
|
20
|
+
// Published by the instrumentation when the SDK's terminationManager.terminate() is called.
|
|
21
|
+
// The instrumentation owns the wrapping; this plugin only reacts.
|
|
22
|
+
const TERMINATE_CHANNEL = 'apm:aws-durable-execution-sdk-js:terminate'
|
|
23
|
+
|
|
24
|
+
class AwsDurableExecutionSdkJsHandlerPlugin extends TracingPlugin {
|
|
25
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
26
|
+
static type = 'serverless'
|
|
27
|
+
static kind = 'internal'
|
|
28
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:withDurableExecution'
|
|
29
|
+
|
|
30
|
+
constructor (...args) {
|
|
31
|
+
super(...args)
|
|
32
|
+
// Gate the subscription on the feature flag: the instrumentation only wraps terminate() while
|
|
33
|
+
// this channel has subscribers, so not subscribing keeps the wrapping off entirely.
|
|
34
|
+
if (this._tracerConfig.DD_DURABLE_CROSS_INVOCATION_TRACING_ENABLED) {
|
|
35
|
+
this.addSub(TERMINATE_CHANNEL, ctx => this.#onTerminate(ctx))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bindStart (ctx) {
|
|
40
|
+
const args = ctx.arguments || []
|
|
41
|
+
const event = args[0]
|
|
42
|
+
const durableExecutionMode = args[3]
|
|
43
|
+
const handler = args[5]
|
|
44
|
+
|
|
45
|
+
const meta = {
|
|
46
|
+
'aws.durable.replayed': durableExecutionMode === 'ReplayMode' ? 'true' : 'false',
|
|
47
|
+
}
|
|
48
|
+
const arn = event?.DurableExecutionArn
|
|
49
|
+
if (arn) {
|
|
50
|
+
meta['aws.durable.execution_arn'] = arn
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.startSpan(this.operationName(), {
|
|
54
|
+
resource: handler?.name,
|
|
55
|
+
kind: this.constructor.kind,
|
|
56
|
+
meta,
|
|
57
|
+
}, ctx)
|
|
58
|
+
|
|
59
|
+
return ctx.currentStore
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fired (synchronously, before the SDK's terminate() runs) when the execution suspends. On a
|
|
63
|
+
// PENDING reason we persist the current trace context as a `_datadog` checkpoint, which
|
|
64
|
+
// subsequent invocations consume to extract the parent trace context. `ctx` is the shared
|
|
65
|
+
// withDurableExecution context: bindStart put the execute span on it, and the instrumentation
|
|
66
|
+
// put the captured durableContext and termination reason on it.
|
|
67
|
+
#onTerminate (ctx) {
|
|
68
|
+
const reason = ctx.terminationReason ?? DEFAULT_TERMINATION_REASON
|
|
69
|
+
if (!PENDING_TERMINATION_REASONS.has(reason)) return
|
|
70
|
+
void maybeSaveCheckpoint(this.tracer, ctx)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
asyncEnd (ctx) {
|
|
74
|
+
const span = ctx?.currentStore?.span
|
|
75
|
+
const status = ctx?.result?.Status
|
|
76
|
+
if (span && typeof status === 'string') {
|
|
77
|
+
span.setTag('aws.durable.invocation_status', status.toLowerCase())
|
|
78
|
+
}
|
|
79
|
+
// Operation child spans rely on user code awaiting the returned DurablePromise to settle;
|
|
80
|
+
// suspended (PENDING) ops never settle, and fire-and-forget ops on terminal handler exits
|
|
81
|
+
// are never awaited at all. Finish any still-open owned children so the trace can flush.
|
|
82
|
+
if (span) finishOpenChildSpans(span)
|
|
83
|
+
super.finish(ctx)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function finishOpenChildSpans (executeSpan) {
|
|
88
|
+
const trace = executeSpan?._spanContext?._trace
|
|
89
|
+
if (!trace?.started) return
|
|
90
|
+
|
|
91
|
+
for (const span of trace.started) {
|
|
92
|
+
if (span === executeSpan) continue
|
|
93
|
+
if (span._integrationName !== AwsDurableExecutionSdkJsHandlerPlugin.id) continue
|
|
94
|
+
if (span._duration === undefined) {
|
|
95
|
+
span.finish()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Save state is kept on the shared `ctx` so repeated terminate() calls within one execution
|
|
101
|
+
// save at most once. The execute span is also the anchor we propagate, so its span id is the
|
|
102
|
+
// `firstExecutionSpanId` passed downstream.
|
|
103
|
+
function maybeSaveCheckpoint (tracer, ctx) {
|
|
104
|
+
if (ctx.checkpointSaved || ctx.checkpointSavePromise) return ctx.checkpointSavePromise
|
|
105
|
+
|
|
106
|
+
const span = ctx.currentStore?.span
|
|
107
|
+
const durableContext = ctx.durableContext
|
|
108
|
+
if (!span || !durableContext) return
|
|
109
|
+
|
|
110
|
+
// Fire-and-forget boundary (#onTerminate calls us with `void`): swallow every failure here so a
|
|
111
|
+
// rejected checkpoint-manager call can never surface as an unhandled rejection in customer code.
|
|
112
|
+
ctx.checkpointSavePromise = saveTraceContextCheckpointIfUpdated(
|
|
113
|
+
tracer,
|
|
114
|
+
span,
|
|
115
|
+
durableContext,
|
|
116
|
+
span.context?.()?.toSpanId?.(),
|
|
117
|
+
ctx.arguments?.[0],
|
|
118
|
+
).catch(error => {
|
|
119
|
+
log.debug('Failed to save trace context checkpoint', error)
|
|
120
|
+
}).finally(() => {
|
|
121
|
+
ctx.checkpointSaved = true
|
|
122
|
+
ctx.checkpointSavePromise = undefined
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return ctx.checkpointSavePromise
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = AwsDurableExecutionSdkJsHandlerPlugin
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
|
+
const checkpointPlugin = require('./checkpoint')
|
|
5
|
+
const clientPlugin = require('./client')
|
|
6
|
+
const contextPlugins = require('./context')
|
|
7
|
+
const handlerPlugin = require('./handler')
|
|
8
|
+
|
|
9
|
+
class AwsDurableExecutionSdkJsPlugin extends CompositePlugin {
|
|
10
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
11
|
+
static plugins = {
|
|
12
|
+
handler: handlerPlugin,
|
|
13
|
+
client: clientPlugin,
|
|
14
|
+
checkpoint: checkpointPlugin,
|
|
15
|
+
...contextPlugins,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = AwsDurableExecutionSdkJsPlugin
|