dd-trace 5.30.0 → 5.32.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.
Files changed (74) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +9 -7
  3. package/package.json +7 -6
  4. package/packages/datadog-core/src/storage.js +11 -2
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +14 -5
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  9. package/packages/datadog-instrumentations/src/jest.js +70 -36
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
  11. package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
  12. package/packages/datadog-instrumentations/src/openai.js +2 -0
  13. package/packages/datadog-instrumentations/src/vitest.js +107 -59
  14. package/packages/datadog-instrumentations/src/vm.js +49 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
  17. package/packages/datadog-plugin-cucumber/src/index.js +30 -32
  18. package/packages/datadog-plugin-jest/src/index.js +34 -37
  19. package/packages/datadog-plugin-langchain/src/index.js +12 -80
  20. package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
  21. package/packages/datadog-plugin-mocha/src/index.js +18 -36
  22. package/packages/datadog-plugin-vitest/src/index.js +20 -34
  23. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
  29. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
  30. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
  31. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
  32. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
  33. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
  34. package/packages/dd-trace/src/config.js +39 -3
  35. package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
  36. package/packages/dd-trace/src/crashtracking/noop.js +3 -0
  37. package/packages/dd-trace/src/datastreams/fnv.js +1 -1
  38. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
  39. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
  42. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
  43. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
  44. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
  45. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
  46. package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -11
  47. package/packages/dd-trace/src/debugger/index.js +2 -13
  48. package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
  49. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
  50. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
  51. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
  52. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
  53. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
  54. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
  55. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
  56. package/packages/dd-trace/src/llmobs/sdk.js +90 -26
  57. package/packages/dd-trace/src/llmobs/tagger.js +11 -3
  58. package/packages/dd-trace/src/llmobs/util.js +7 -1
  59. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
  60. package/packages/dd-trace/src/log/index.js +8 -9
  61. package/packages/dd-trace/src/noop/proxy.js +2 -2
  62. package/packages/dd-trace/src/noop/span.js +1 -1
  63. package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
  64. package/packages/dd-trace/src/opentracing/span.js +11 -1
  65. package/packages/dd-trace/src/opentracing/span_context.js +12 -0
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
  67. package/packages/dd-trace/src/plugins/util/test.js +42 -12
  68. package/packages/dd-trace/src/priority_sampler.js +7 -2
  69. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
  70. package/packages/dd-trace/src/profiling/profiler.js +11 -8
  71. package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
  72. package/packages/dd-trace/src/proxy.js +6 -3
  73. package/packages/dd-trace/src/scope.js +1 -1
  74. package/packages/dd-trace/src/telemetry/index.js +2 -0
@@ -26,12 +26,7 @@ const {
26
26
  TEST_MODULE,
27
27
  TEST_MODULE_ID,
28
28
  TEST_SUITE,
29
- CUCUMBER_IS_PARALLEL,
30
- TEST_NAME,
31
- DI_ERROR_DEBUG_INFO_CAPTURED,
32
- DI_DEBUG_ERROR_SNAPSHOT_ID,
33
- DI_DEBUG_ERROR_FILE,
34
- DI_DEBUG_ERROR_LINE
29
+ CUCUMBER_IS_PARALLEL
35
30
  } = require('../../dd-trace/src/plugins/util/test')
36
31
  const { RESOURCE_NAME } = require('../../../ext/tags')
37
32
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -50,8 +45,8 @@ const {
50
45
  } = require('../../dd-trace/src/ci-visibility/telemetry')
51
46
  const id = require('../../dd-trace/src/id')
52
47
 
48
+ const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
53
49
  const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
54
- const debuggerParameterPerTest = new Map()
55
50
 
56
51
  function getTestSuiteTags (testSuiteSpan) {
57
52
  const suiteTags = {
@@ -210,7 +205,13 @@ class CucumberPlugin extends CiPlugin {
210
205
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
211
206
  })
212
207
 
213
- this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine, isParallel }) => {
208
+ this.addSub('ci:cucumber:test:start', ({
209
+ testName,
210
+ testFileAbsolutePath,
211
+ testSourceLine,
212
+ isParallel,
213
+ promises
214
+ }) => {
214
215
  const store = storage.getStore()
215
216
  const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
216
217
  const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
@@ -227,38 +228,30 @@ class CucumberPlugin extends CiPlugin {
227
228
 
228
229
  this.enter(testSpan, store)
229
230
 
230
- const debuggerParameters = debuggerParameterPerTest.get(testName)
231
-
232
- if (debuggerParameters) {
233
- const spanContext = testSpan.context()
234
-
235
- // TODO: handle race conditions with this.retriedTestIds
236
- this.retriedTestIds = {
237
- spanId: spanContext.toSpanId(),
238
- traceId: spanContext.toTraceId()
239
- }
240
- const { snapshotId, file, line } = debuggerParameters
241
-
242
- // TODO: should these be added on test:end if and only if the probe is hit?
243
- // Sync issues: `hitProbePromise` might be resolved after the test ends
244
- testSpan.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
245
- testSpan.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
246
- testSpan.setTag(DI_DEBUG_ERROR_FILE, file)
247
- testSpan.setTag(DI_DEBUG_ERROR_LINE, line)
231
+ this.activeTestSpan = testSpan
232
+ // Time we give the breakpoint to be hit
233
+ if (promises && this.runningTestProbeId) {
234
+ promises.hitBreakpointPromise = new Promise((resolve) => {
235
+ setTimeout(resolve, BREAKPOINT_HIT_GRACE_PERIOD_MS)
236
+ })
248
237
  }
249
238
  })
250
239
 
251
- this.addSub('ci:cucumber:test:retry', ({ isRetry, error }) => {
240
+ this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error }) => {
252
241
  const store = storage.getStore()
253
242
  const span = store.span
254
- if (isRetry) {
243
+ if (!isFirstAttempt) {
255
244
  span.setTag(TEST_IS_RETRY, 'true')
256
245
  }
257
246
  span.setTag('error', error)
258
- if (this.di && error && this.libraryConfig?.isDiEnabled) {
259
- const testName = span.context()._tags[TEST_NAME]
260
- const debuggerParameters = this.addDiProbe(error)
261
- debuggerParameterPerTest.set(testName, debuggerParameters)
247
+ if (isFirstAttempt && this.di && error && this.libraryConfig?.isDiEnabled) {
248
+ const probeInformation = this.addDiProbe(error)
249
+ if (probeInformation) {
250
+ const { probeId, stackIndex } = probeInformation
251
+ this.runningTestProbeId = probeId
252
+ this.testErrorStackIndex = stackIndex
253
+ // TODO: we're not waiting for setProbePromise to be resolved, so there might be race conditions
254
+ }
262
255
  }
263
256
  span.setTag(TEST_STATUS, 'fail')
264
257
  span.finish()
@@ -363,6 +356,11 @@ class CucumberPlugin extends CiPlugin {
363
356
  if (isCucumberWorker) {
364
357
  this.tracer._exporter.flush()
365
358
  }
359
+ this.activeTestSpan = null
360
+ if (this.runningTestProbeId) {
361
+ this.removeDiProbe(this.runningTestProbeId)
362
+ this.runningTestProbeId = null
363
+ }
366
364
  }
367
365
  })
368
366
 
@@ -23,11 +23,7 @@ const {
23
23
  JEST_DISPLAY_NAME,
24
24
  TEST_IS_RUM_ACTIVE,
25
25
  TEST_BROWSER_DRIVER,
26
- DI_ERROR_DEBUG_INFO_CAPTURED,
27
- DI_DEBUG_ERROR_SNAPSHOT_ID,
28
- DI_DEBUG_ERROR_FILE,
29
- DI_DEBUG_ERROR_LINE,
30
- TEST_NAME
26
+ getFormattedError
31
27
  } = require('../../dd-trace/src/plugins/util/test')
32
28
  const { COMPONENT } = require('../../dd-trace/src/constants')
33
29
  const id = require('../../dd-trace/src/id')
@@ -44,11 +40,20 @@ const {
44
40
  } = require('../../dd-trace/src/ci-visibility/telemetry')
45
41
 
46
42
  const isJestWorker = !!process.env.JEST_WORKER_ID
47
- const debuggerParameterPerTest = new Map()
48
43
 
49
44
  // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
50
45
  const CHILD_MESSAGE_END = 2
51
46
 
47
+ function withTimeout (promise, timeoutMs) {
48
+ return new Promise(resolve => {
49
+ // Set a timeout to resolve after 1s
50
+ setTimeout(resolve, timeoutMs)
51
+
52
+ // Also resolve if the original promise resolves
53
+ promise.then(resolve)
54
+ })
55
+ }
56
+
52
57
  class JestPlugin extends CiPlugin {
53
58
  static get id () {
54
59
  return 'jest'
@@ -260,6 +265,12 @@ class JestPlugin extends CiPlugin {
260
265
  })
261
266
  })
262
267
 
268
+ this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
269
+ JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
270
+ this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
271
+ })
272
+ })
273
+
263
274
  this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
264
275
  this.testSuiteSpan.setTag(TEST_STATUS, status)
265
276
  if (error) {
@@ -308,32 +319,10 @@ class JestPlugin extends CiPlugin {
308
319
  const span = this.startTestSpan(test)
309
320
 
310
321
  this.enter(span, store)
311
-
312
- const { name: testName } = test
313
-
314
- const debuggerParameters = debuggerParameterPerTest.get(testName)
315
-
316
- // If we have a debugger probe, we need to add the snapshot id to the span
317
- if (debuggerParameters) {
318
- const spanContext = span.context()
319
-
320
- // TODO: handle race conditions with this.retriedTestIds
321
- this.retriedTestIds = {
322
- spanId: spanContext.toSpanId(),
323
- traceId: spanContext.toTraceId()
324
- }
325
- const { snapshotId, file, line } = debuggerParameters
326
-
327
- // TODO: should these be added on test:end if and only if the probe is hit?
328
- // Sync issues: `hitProbePromise` might be resolved after the test ends
329
- span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
330
- span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
331
- span.setTag(DI_DEBUG_ERROR_FILE, file)
332
- span.setTag(DI_DEBUG_ERROR_LINE, line)
333
- }
322
+ this.activeTestSpan = span
334
323
  })
335
324
 
336
- this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
325
+ this.addSub('ci:jest:test:finish', ({ status, testStartLine, promises, shouldRemoveProbe }) => {
337
326
  const span = storage.getStore().span
338
327
  span.setTag(TEST_STATUS, status)
339
328
  if (testStartLine) {
@@ -354,20 +343,28 @@ class JestPlugin extends CiPlugin {
354
343
 
355
344
  span.finish()
356
345
  finishAllTraceSpans(span)
346
+ this.activeTestSpan = null
347
+ if (shouldRemoveProbe && this.runningTestProbeId) {
348
+ promises.isProbeRemoved = withTimeout(this.removeDiProbe(this.runningTestProbeId), 2000)
349
+ this.runningTestProbeId = null
350
+ }
357
351
  })
358
352
 
359
- this.addSub('ci:jest:test:err', ({ error, willBeRetried, probe, isDiEnabled }) => {
353
+ this.addSub('ci:jest:test:err', ({ error, shouldSetProbe, promises }) => {
360
354
  if (error) {
361
355
  const store = storage.getStore()
362
356
  if (store && store.span) {
363
357
  const span = store.span
364
358
  span.setTag(TEST_STATUS, 'fail')
365
- span.setTag('error', error)
366
- if (willBeRetried && this.di && isDiEnabled) {
367
- // if we use numTestExecutions, we have to remove the breakpoint after each execution
368
- const testName = span.context()._tags[TEST_NAME]
369
- const debuggerParameters = this.addDiProbe(error, probe)
370
- debuggerParameterPerTest.set(testName, debuggerParameters)
359
+ span.setTag('error', getFormattedError(error, this.repositoryRoot))
360
+ if (shouldSetProbe) {
361
+ const probeInformation = this.addDiProbe(error)
362
+ if (probeInformation) {
363
+ const { probeId, setProbePromise, stackIndex } = probeInformation
364
+ this.runningTestProbeId = probeId
365
+ this.testErrorStackIndex = stackIndex
366
+ promises.isProbeReady = withTimeout(setProbePromise, 2000)
367
+ }
371
368
  }
372
369
  }
373
370
  }
@@ -1,89 +1,21 @@
1
1
  'use strict'
2
2
 
3
- const { MEASURED } = require('../../../ext/tags')
4
- const { storage } = require('../../datadog-core')
5
- const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
3
+ const LangChainTracingPlugin = require('./tracing')
4
+ const LangChainLLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/langchain')
5
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
6
6
 
7
- const API_KEY = 'langchain.request.api_key'
8
- const MODEL = 'langchain.request.model'
9
- const PROVIDER = 'langchain.request.provider'
10
- const TYPE = 'langchain.request.type'
11
-
12
- const LangChainHandler = require('./handlers/default')
13
- const LangChainChatModelHandler = require('./handlers/language_models/chat_model')
14
- const LangChainLLMHandler = require('./handlers/language_models/llm')
15
- const LangChainChainHandler = require('./handlers/chain')
16
- const LangChainEmbeddingHandler = require('./handlers/embedding')
17
-
18
- class LangChainPlugin extends TracingPlugin {
7
+ class LangChainPlugin extends CompositePlugin {
19
8
  static get id () { return 'langchain' }
20
- static get operation () { return 'invoke' }
21
- static get system () { return 'langchain' }
22
- static get prefix () {
23
- return 'tracing:apm:langchain:invoke'
24
- }
25
-
26
- constructor () {
27
- super(...arguments)
28
-
29
- const langchainConfig = this._tracerConfig.langchain || {}
30
- this.handlers = {
31
- chain: new LangChainChainHandler(langchainConfig),
32
- chat_model: new LangChainChatModelHandler(langchainConfig),
33
- llm: new LangChainLLMHandler(langchainConfig),
34
- embedding: new LangChainEmbeddingHandler(langchainConfig),
35
- default: new LangChainHandler(langchainConfig)
9
+ static get plugins () {
10
+ return {
11
+ // ordering here is important - the llm observability plugin must come first
12
+ // so that we can add annotations associated with the span before it finishes.
13
+ // however, because the tracing plugin uses `bindStart` vs the llmobs' `start`,
14
+ // the span is guaranteed to be created in the tracing plugin before the llmobs one is called
15
+ llmobs: LangChainLLMObsPlugin,
16
+ tracing: LangChainTracingPlugin
36
17
  }
37
18
  }
38
-
39
- bindStart (ctx) {
40
- const { resource, type } = ctx
41
- const handler = this.handlers[type]
42
-
43
- const instance = ctx.instance
44
- const apiKey = handler.extractApiKey(instance)
45
- const provider = handler.extractProvider(instance)
46
- const model = handler.extractModel(instance)
47
-
48
- const tags = handler.getSpanStartTags(ctx, provider) || []
49
-
50
- if (apiKey) tags[API_KEY] = apiKey
51
- if (provider) tags[PROVIDER] = provider
52
- if (model) tags[MODEL] = model
53
- if (type) tags[TYPE] = type
54
-
55
- const span = this.startSpan('langchain.request', {
56
- service: this.config.service,
57
- resource,
58
- kind: 'client',
59
- meta: {
60
- [MEASURED]: 1,
61
- ...tags
62
- }
63
- }, false)
64
-
65
- const store = storage.getStore() || {}
66
- ctx.currentStore = { ...store, span }
67
-
68
- return ctx.currentStore
69
- }
70
-
71
- asyncEnd (ctx) {
72
- const span = ctx.currentStore.span
73
-
74
- const { type } = ctx
75
-
76
- const handler = this.handlers[type]
77
- const tags = handler.getSpanEndTags(ctx) || {}
78
-
79
- span.addTags(tags)
80
-
81
- span.finish()
82
- }
83
-
84
- getHandler (type) {
85
- return this.handlers[type] || this.handlers.default
86
- }
87
19
  }
88
20
 
89
21
  module.exports = LangChainPlugin
@@ -0,0 +1,89 @@
1
+ 'use strict'
2
+
3
+ const { MEASURED } = require('../../../ext/tags')
4
+ const { storage } = require('../../datadog-core')
5
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
6
+
7
+ const API_KEY = 'langchain.request.api_key'
8
+ const MODEL = 'langchain.request.model'
9
+ const PROVIDER = 'langchain.request.provider'
10
+ const TYPE = 'langchain.request.type'
11
+
12
+ const LangChainHandler = require('./handlers/default')
13
+ const LangChainChatModelHandler = require('./handlers/language_models/chat_model')
14
+ const LangChainLLMHandler = require('./handlers/language_models/llm')
15
+ const LangChainChainHandler = require('./handlers/chain')
16
+ const LangChainEmbeddingHandler = require('./handlers/embedding')
17
+
18
+ class LangChainTracingPlugin extends TracingPlugin {
19
+ static get id () { return 'langchain' }
20
+ static get operation () { return 'invoke' }
21
+ static get system () { return 'langchain' }
22
+ static get prefix () {
23
+ return 'tracing:apm:langchain:invoke'
24
+ }
25
+
26
+ constructor () {
27
+ super(...arguments)
28
+
29
+ const langchainConfig = this._tracerConfig.langchain || {}
30
+ this.handlers = {
31
+ chain: new LangChainChainHandler(langchainConfig),
32
+ chat_model: new LangChainChatModelHandler(langchainConfig),
33
+ llm: new LangChainLLMHandler(langchainConfig),
34
+ embedding: new LangChainEmbeddingHandler(langchainConfig),
35
+ default: new LangChainHandler(langchainConfig)
36
+ }
37
+ }
38
+
39
+ bindStart (ctx) {
40
+ const { resource, type } = ctx
41
+ const handler = this.handlers[type]
42
+
43
+ const instance = ctx.instance
44
+ const apiKey = handler.extractApiKey(instance)
45
+ const provider = handler.extractProvider(instance)
46
+ const model = handler.extractModel(instance)
47
+
48
+ const tags = handler.getSpanStartTags(ctx, provider) || []
49
+
50
+ if (apiKey) tags[API_KEY] = apiKey
51
+ if (provider) tags[PROVIDER] = provider
52
+ if (model) tags[MODEL] = model
53
+ if (type) tags[TYPE] = type
54
+
55
+ const span = this.startSpan('langchain.request', {
56
+ service: this.config.service,
57
+ resource,
58
+ kind: 'client',
59
+ meta: {
60
+ [MEASURED]: 1,
61
+ ...tags
62
+ }
63
+ }, false)
64
+
65
+ const store = storage.getStore() || {}
66
+ ctx.currentStore = { ...store, span }
67
+
68
+ return ctx.currentStore
69
+ }
70
+
71
+ asyncEnd (ctx) {
72
+ const span = ctx.currentStore.span
73
+
74
+ const { type } = ctx
75
+
76
+ const handler = this.handlers[type]
77
+ const tags = handler.getSpanEndTags(ctx) || {}
78
+
79
+ span.addTags(tags)
80
+
81
+ span.finish()
82
+ }
83
+
84
+ getHandler (type) {
85
+ return this.handlers[type] || this.handlers.default
86
+ }
87
+ }
88
+
89
+ module.exports = LangChainTracingPlugin
@@ -30,12 +30,7 @@ const {
30
30
  TEST_SUITE,
31
31
  MOCHA_IS_PARALLEL,
32
32
  TEST_IS_RUM_ACTIVE,
33
- TEST_BROWSER_DRIVER,
34
- TEST_NAME,
35
- DI_ERROR_DEBUG_INFO_CAPTURED,
36
- DI_DEBUG_ERROR_SNAPSHOT_ID,
37
- DI_DEBUG_ERROR_FILE,
38
- DI_DEBUG_ERROR_LINE
33
+ TEST_BROWSER_DRIVER
39
34
  } = require('../../dd-trace/src/plugins/util/test')
40
35
  const { COMPONENT } = require('../../dd-trace/src/constants')
41
36
  const {
@@ -52,8 +47,6 @@ const {
52
47
  const id = require('../../dd-trace/src/id')
53
48
  const log = require('../../dd-trace/src/log')
54
49
 
55
- const debuggerParameterPerTest = new Map()
56
-
57
50
  function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
58
51
  const testSuiteSpanContext = testSuiteSpan.context()
59
52
  const suiteTags = {
@@ -192,36 +185,15 @@ class MochaPlugin extends CiPlugin {
192
185
  const store = storage.getStore()
193
186
  const span = this.startTestSpan(testInfo)
194
187
 
195
- const { testName } = testInfo
196
-
197
- const debuggerParameters = debuggerParameterPerTest.get(testName)
198
-
199
- if (debuggerParameters) {
200
- const spanContext = span.context()
201
-
202
- // TODO: handle race conditions with this.retriedTestIds
203
- this.retriedTestIds = {
204
- spanId: spanContext.toSpanId(),
205
- traceId: spanContext.toTraceId()
206
- }
207
- const { snapshotId, file, line } = debuggerParameters
208
-
209
- // TODO: should these be added on test:end if and only if the probe is hit?
210
- // Sync issues: `hitProbePromise` might be resolved after the test ends
211
- span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
212
- span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
213
- span.setTag(DI_DEBUG_ERROR_FILE, file)
214
- span.setTag(DI_DEBUG_ERROR_LINE, line)
215
- }
216
-
217
188
  this.enter(span, store)
189
+ this.activeTestSpan = span
218
190
  })
219
191
 
220
192
  this.addSub('ci:mocha:worker:finish', () => {
221
193
  this.tracer._exporter.flush()
222
194
  })
223
195
 
224
- this.addSub('ci:mocha:test:finish', ({ status, hasBeenRetried }) => {
196
+ this.addSub('ci:mocha:test:finish', ({ status, hasBeenRetried, isLastRetry }) => {
225
197
  const store = storage.getStore()
226
198
  const span = store?.span
227
199
 
@@ -245,6 +217,11 @@ class MochaPlugin extends CiPlugin {
245
217
 
246
218
  span.finish()
247
219
  finishAllTraceSpans(span)
220
+ this.activeTestSpan = null
221
+ if (this.di && this.libraryConfig?.isDiEnabled && this.runningTestProbeId && isLastRetry) {
222
+ this.removeDiProbe(this.runningTestProbeId)
223
+ this.runningTestProbeId = null
224
+ }
248
225
  }
249
226
  })
250
227
 
@@ -271,7 +248,7 @@ class MochaPlugin extends CiPlugin {
271
248
  }
272
249
  })
273
250
 
274
- this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err }) => {
251
+ this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err, test }) => {
275
252
  const store = storage.getStore()
276
253
  const span = store?.span
277
254
  if (span) {
@@ -294,10 +271,15 @@ class MochaPlugin extends CiPlugin {
294
271
  browserDriver: spanTags[TEST_BROWSER_DRIVER]
295
272
  }
296
273
  )
297
- if (willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
298
- const testName = span.context()._tags[TEST_NAME]
299
- const debuggerParameters = this.addDiProbe(err)
300
- debuggerParameterPerTest.set(testName, debuggerParameters)
274
+ if (isFirstAttempt && willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
275
+ const probeInformation = this.addDiProbe(err)
276
+ if (probeInformation) {
277
+ const { probeId, stackIndex } = probeInformation
278
+ this.runningTestProbeId = probeId
279
+ this.testErrorStackIndex = stackIndex
280
+ test._ddShouldWaitForHitProbe = true
281
+ // TODO: we're not waiting for setProbePromise to be resolved, so there might be race conditions
282
+ }
301
283
  }
302
284
 
303
285
  span.finish()
@@ -17,12 +17,7 @@ const {
17
17
  TEST_SOURCE_START,
18
18
  TEST_IS_NEW,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
- TEST_EARLY_FLAKE_ABORT_REASON,
21
- TEST_NAME,
22
- DI_ERROR_DEBUG_INFO_CAPTURED,
23
- DI_DEBUG_ERROR_SNAPSHOT_ID,
24
- DI_DEBUG_ERROR_FILE,
25
- DI_DEBUG_ERROR_LINE
20
+ TEST_EARLY_FLAKE_ABORT_REASON
26
21
  } = require('../../dd-trace/src/plugins/util/test')
27
22
  const { COMPONENT } = require('../../dd-trace/src/constants')
28
23
  const {
@@ -36,8 +31,6 @@ const {
36
31
  // This is because there's some loss of resolution.
37
32
  const MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION = 5
38
33
 
39
- const debuggerParameterPerTest = new Map()
40
-
41
34
  class VitestPlugin extends CiPlugin {
42
35
  static get id () {
43
36
  return 'vitest'
@@ -67,7 +60,7 @@ class VitestPlugin extends CiPlugin {
67
60
  onDone(isFaulty)
68
61
  })
69
62
 
70
- this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry, isNew }) => {
63
+ this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry, isNew, mightHitProbe }) => {
71
64
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
72
65
  const store = storage.getStore()
73
66
 
@@ -88,27 +81,13 @@ class VitestPlugin extends CiPlugin {
88
81
  extraTags
89
82
  )
90
83
 
91
- const debuggerParameters = debuggerParameterPerTest.get(testName)
92
-
93
- if (debuggerParameters) {
94
- const spanContext = span.context()
95
-
96
- // TODO: handle race conditions with this.retriedTestIds
97
- this.retriedTestIds = {
98
- spanId: spanContext.toSpanId(),
99
- traceId: spanContext.toTraceId()
100
- }
101
- const { snapshotId, file, line } = debuggerParameters
84
+ this.enter(span, store)
102
85
 
103
- // TODO: should these be added on test:end if and only if the probe is hit?
104
- // Sync issues: `hitProbePromise` might be resolved after the test ends
105
- span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
106
- span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
107
- span.setTag(DI_DEBUG_ERROR_FILE, file)
108
- span.setTag(DI_DEBUG_ERROR_LINE, line)
86
+ // TODO: there might be multiple tests for which mightHitProbe is true, so activeTestSpan
87
+ // might be wrongly overwritten.
88
+ if (mightHitProbe) {
89
+ this.activeTestSpan = span
109
90
  }
110
-
111
- this.enter(span, store)
112
91
  })
113
92
 
114
93
  this.addSub('ci:vitest:test:finish-time', ({ status, task }) => {
@@ -137,15 +116,19 @@ class VitestPlugin extends CiPlugin {
137
116
  }
138
117
  })
139
118
 
140
- this.addSub('ci:vitest:test:error', ({ duration, error, willBeRetried, probe, isDiEnabled }) => {
119
+ this.addSub('ci:vitest:test:error', ({ duration, error, shouldSetProbe, promises }) => {
141
120
  const store = storage.getStore()
142
121
  const span = store?.span
143
122
 
144
123
  if (span) {
145
- if (willBeRetried && this.di && isDiEnabled) {
146
- const testName = span.context()._tags[TEST_NAME]
147
- const debuggerParameters = this.addDiProbe(error, probe)
148
- debuggerParameterPerTest.set(testName, debuggerParameters)
124
+ if (shouldSetProbe && this.di) {
125
+ const probeInformation = this.addDiProbe(error)
126
+ if (probeInformation) {
127
+ const { probeId, stackIndex, setProbePromise } = probeInformation
128
+ this.runningTestProbeId = probeId
129
+ this.testErrorStackIndex = stackIndex
130
+ promises.setProbePromise = setProbePromise
131
+ }
149
132
  }
150
133
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
151
134
  hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
@@ -158,7 +141,7 @@ class VitestPlugin extends CiPlugin {
158
141
  if (duration) {
159
142
  span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
160
143
  } else {
161
- span.finish() // retries will not have a duration
144
+ span.finish() // `duration` is empty for retries, so we'll use clock time
162
145
  }
163
146
  finishAllTraceSpans(span)
164
147
  }
@@ -242,6 +225,9 @@ class VitestPlugin extends CiPlugin {
242
225
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
243
226
  // TODO: too frequent flush - find for method in worker to decrease frequency
244
227
  this.tracer._exporter.flush(onFinish)
228
+ if (this.runningTestProbeId) {
229
+ this.removeDiProbe(this.runningTestProbeId)
230
+ }
245
231
  })
246
232
 
247
233
  this.addSub('ci:vitest:test-suite:error', ({ error }) => {
@@ -17,6 +17,7 @@ module.exports = {
17
17
  SSRF: require('./ssrf-analyzer'),
18
18
  TEMPLATE_INJECTION_ANALYZER: require('./template-injection-analyzer'),
19
19
  UNVALIDATED_REDIRECT_ANALYZER: require('./unvalidated-redirect-analyzer'),
20
+ UNTRUSTED_DESERIALIZATION_ANALYZER: require('./untrusted-deserialization-analyzer'),
20
21
  WEAK_CIPHER_ANALYZER: require('./weak-cipher-analyzer'),
21
22
  WEAK_HASH_ANALYZER: require('./weak-hash-analyzer'),
22
23
  WEAK_RANDOMNESS_ANALYZER: require('./weak-randomness-analyzer'),
@@ -10,6 +10,8 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
10
10
 
11
11
  onConfigure () {
12
12
  this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
13
+ this.addSub('datadog:vm:run-script:start', ({ code }) => this.analyze(code))
14
+ this.addSub('datadog:vm:source-text-module:start', ({ code }) => this.analyze(code))
13
15
  }
14
16
 
15
17
  _areRangesVulnerable () {
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { UNTRUSTED_DESERIALIZATION } = require('../vulnerabilities')
5
+
6
+ class UntrustedDeserializationAnalyzer extends InjectionAnalyzer {
7
+ constructor () {
8
+ super(UNTRUSTED_DESERIALIZATION)
9
+ }
10
+
11
+ onConfigure () {
12
+ this.addSub('datadog:node-serialize:unserialize:start', ({ obj }) => this.analyze(obj))
13
+ }
14
+ }
15
+
16
+ module.exports = new UntrustedDeserializationAnalyzer()