dd-trace 5.31.0 → 5.33.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 (85) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +17 -14
  3. package/index.d.ts +11 -1
  4. package/package.json +6 -5
  5. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
  6. package/packages/datadog-instrumentations/src/cucumber.js +31 -14
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  8. package/packages/datadog-instrumentations/src/jest.js +105 -56
  9. package/packages/datadog-instrumentations/src/mocha/main.js +9 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +27 -9
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +4 -2
  12. package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
  13. package/packages/datadog-instrumentations/src/openai.js +2 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +8 -3
  15. package/packages/datadog-instrumentations/src/vitest.js +134 -62
  16. package/packages/datadog-instrumentations/src/vm.js +49 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
  19. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +287 -0
  20. package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
  21. package/packages/datadog-plugin-cucumber/src/index.js +31 -31
  22. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
  23. package/packages/datadog-plugin-cypress/src/support.js +6 -2
  24. package/packages/datadog-plugin-fetch/src/index.js +3 -3
  25. package/packages/datadog-plugin-http/src/client.js +5 -33
  26. package/packages/datadog-plugin-jest/src/index.js +37 -37
  27. package/packages/datadog-plugin-langchain/src/index.js +12 -80
  28. package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
  29. package/packages/datadog-plugin-mocha/src/index.js +19 -35
  30. package/packages/datadog-plugin-playwright/src/index.js +3 -1
  31. package/packages/datadog-plugin-vitest/src/index.js +33 -35
  32. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  33. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
  35. package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
  36. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
  37. package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
  38. package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
  39. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
  40. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
  41. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  42. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
  43. package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
  44. package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
  45. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
  46. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
  47. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
  49. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
  51. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
  52. package/packages/dd-trace/src/config.js +43 -3
  53. package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
  54. package/packages/dd-trace/src/crashtracking/noop.js +3 -0
  55. package/packages/dd-trace/src/datastreams/fnv.js +1 -1
  56. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
  57. package/packages/dd-trace/src/debugger/devtools_client/config.js +1 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -13
  60. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -8
  61. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
  62. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
  63. package/packages/dd-trace/src/debugger/devtools_client/status.js +12 -10
  64. package/packages/dd-trace/src/debugger/index.js +2 -13
  65. package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
  66. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
  67. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
  68. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
  69. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
  70. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
  71. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
  72. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
  73. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
  74. package/packages/dd-trace/src/llmobs/tagger.js +11 -3
  75. package/packages/dd-trace/src/llmobs/util.js +7 -1
  76. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
  78. package/packages/dd-trace/src/plugins/ci_plugin.js +58 -27
  79. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
  80. package/packages/dd-trace/src/plugins/util/test.js +44 -12
  81. package/packages/dd-trace/src/priority_sampler.js +4 -1
  82. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
  83. package/packages/dd-trace/src/profiling/profiler.js +11 -8
  84. package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
  85. package/packages/dd-trace/src/proxy.js +6 -3
@@ -59,6 +59,11 @@ class HttpClientPlugin extends ClientPlugin {
59
59
  }
60
60
 
61
61
  if (this.shouldInjectTraceHeaders(options, uri)) {
62
+ // Clone the headers object in case an upstream lib has a reference to the original headers
63
+ // Implemented due to aws-sdk issue where request signing is broken if we mutate the headers
64
+ // Explained further in:
65
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1609#issuecomment-1826167348
66
+ options.headers = Object.assign({}, options.headers)
62
67
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
68
  }
64
69
 
@@ -72,10 +77,6 @@ class HttpClientPlugin extends ClientPlugin {
72
77
  }
73
78
 
74
79
  shouldInjectTraceHeaders (options, uri) {
75
- if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
- return false
77
- }
78
-
79
80
  if (!this.config.propagationFilter(uri)) {
80
81
  return false
81
82
  }
@@ -212,31 +213,6 @@ function getHooks (config) {
212
213
  return { request }
213
214
  }
214
215
 
215
- function hasAmazonSignature (options) {
216
- if (!options) {
217
- return false
218
- }
219
-
220
- if (options.headers) {
221
- const headers = Object.keys(options.headers)
222
- .reduce((prev, next) => Object.assign(prev, {
223
- [next.toLowerCase()]: options.headers[next]
224
- }), {})
225
-
226
- if (headers['x-amz-signature']) {
227
- return true
228
- }
229
-
230
- if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
231
- return true
232
- }
233
- }
234
-
235
- const search = options.search || options.path
236
-
237
- return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
238
- }
239
-
240
216
  function extractSessionDetails (options) {
241
217
  if (typeof options === 'string') {
242
218
  return new URL(options).host
@@ -248,8 +224,4 @@ function extractSessionDetails (options) {
248
224
  return { host, port }
249
225
  }
250
226
 
251
- function startsWith (searchString) {
252
- return value => String(value).startsWith(searchString)
253
- }
254
-
255
227
  module.exports = HttpClientPlugin
@@ -23,11 +23,8 @@ 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,
27
+ TEST_RETRY_REASON
31
28
  } = require('../../dd-trace/src/plugins/util/test')
32
29
  const { COMPONENT } = require('../../dd-trace/src/constants')
33
30
  const id = require('../../dd-trace/src/id')
@@ -44,11 +41,20 @@ const {
44
41
  } = require('../../dd-trace/src/ci-visibility/telemetry')
45
42
 
46
43
  const isJestWorker = !!process.env.JEST_WORKER_ID
47
- const debuggerParameterPerTest = new Map()
48
44
 
49
45
  // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
50
46
  const CHILD_MESSAGE_END = 2
51
47
 
48
+ function withTimeout (promise, timeoutMs) {
49
+ return new Promise(resolve => {
50
+ // Set a timeout to resolve after 1s
51
+ setTimeout(resolve, timeoutMs)
52
+
53
+ // Also resolve if the original promise resolves
54
+ promise.then(resolve)
55
+ })
56
+ }
57
+
52
58
  class JestPlugin extends CiPlugin {
53
59
  static get id () {
54
60
  return 'jest'
@@ -162,6 +168,7 @@ class JestPlugin extends CiPlugin {
162
168
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
163
169
  config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
164
170
  config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
171
+ config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
165
172
  })
166
173
  })
167
174
 
@@ -260,6 +267,12 @@ class JestPlugin extends CiPlugin {
260
267
  })
261
268
  })
262
269
 
270
+ this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
271
+ JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
272
+ this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
273
+ })
274
+ })
275
+
263
276
  this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
264
277
  this.testSuiteSpan.setTag(TEST_STATUS, status)
265
278
  if (error) {
@@ -308,32 +321,10 @@ class JestPlugin extends CiPlugin {
308
321
  const span = this.startTestSpan(test)
309
322
 
310
323
  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
- }
324
+ this.activeTestSpan = span
334
325
  })
335
326
 
336
- this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
327
+ this.addSub('ci:jest:test:finish', ({ status, testStartLine, promises, shouldRemoveProbe }) => {
337
328
  const span = storage.getStore().span
338
329
  span.setTag(TEST_STATUS, status)
339
330
  if (testStartLine) {
@@ -354,20 +345,28 @@ class JestPlugin extends CiPlugin {
354
345
 
355
346
  span.finish()
356
347
  finishAllTraceSpans(span)
348
+ this.activeTestSpan = null
349
+ if (shouldRemoveProbe && this.runningTestProbeId) {
350
+ promises.isProbeRemoved = withTimeout(this.removeDiProbe(this.runningTestProbeId), 2000)
351
+ this.runningTestProbeId = null
352
+ }
357
353
  })
358
354
 
359
- this.addSub('ci:jest:test:err', ({ error, willBeRetried, probe, isDiEnabled }) => {
355
+ this.addSub('ci:jest:test:err', ({ error, shouldSetProbe, promises }) => {
360
356
  if (error) {
361
357
  const store = storage.getStore()
362
358
  if (store && store.span) {
363
359
  const span = store.span
364
360
  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)
361
+ span.setTag('error', getFormattedError(error, this.repositoryRoot))
362
+ if (shouldSetProbe) {
363
+ const probeInformation = this.addDiProbe(error)
364
+ if (probeInformation) {
365
+ const { probeId, setProbePromise, stackIndex } = probeInformation
366
+ this.runningTestProbeId = probeId
367
+ this.testErrorStackIndex = stackIndex
368
+ promises.isProbeReady = withTimeout(setProbePromise, 2000)
369
+ }
371
370
  }
372
371
  }
373
372
  }
@@ -413,6 +412,7 @@ class JestPlugin extends CiPlugin {
413
412
  extraTags[TEST_IS_NEW] = 'true'
414
413
  if (isEfdRetry) {
415
414
  extraTags[TEST_IS_RETRY] = 'true'
415
+ extraTags[TEST_RETRY_REASON] = 'efd'
416
416
  }
417
417
  }
418
418
 
@@ -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
@@ -31,11 +31,7 @@ const {
31
31
  MOCHA_IS_PARALLEL,
32
32
  TEST_IS_RUM_ACTIVE,
33
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
34
+ TEST_RETRY_REASON
39
35
  } = require('../../dd-trace/src/plugins/util/test')
40
36
  const { COMPONENT } = require('../../dd-trace/src/constants')
41
37
  const {
@@ -52,8 +48,6 @@ const {
52
48
  const id = require('../../dd-trace/src/id')
53
49
  const log = require('../../dd-trace/src/log')
54
50
 
55
- const debuggerParameterPerTest = new Map()
56
-
57
51
  function getTestSuiteLevelVisibilityTags (testSuiteSpan) {
58
52
  const testSuiteSpanContext = testSuiteSpan.context()
59
53
  const suiteTags = {
@@ -192,36 +186,15 @@ class MochaPlugin extends CiPlugin {
192
186
  const store = storage.getStore()
193
187
  const span = this.startTestSpan(testInfo)
194
188
 
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
189
  this.enter(span, store)
190
+ this.activeTestSpan = span
218
191
  })
219
192
 
220
193
  this.addSub('ci:mocha:worker:finish', () => {
221
194
  this.tracer._exporter.flush()
222
195
  })
223
196
 
224
- this.addSub('ci:mocha:test:finish', ({ status, hasBeenRetried }) => {
197
+ this.addSub('ci:mocha:test:finish', ({ status, hasBeenRetried, isLastRetry }) => {
225
198
  const store = storage.getStore()
226
199
  const span = store?.span
227
200
 
@@ -245,6 +218,11 @@ class MochaPlugin extends CiPlugin {
245
218
 
246
219
  span.finish()
247
220
  finishAllTraceSpans(span)
221
+ this.activeTestSpan = null
222
+ if (this.di && this.libraryConfig?.isDiEnabled && this.runningTestProbeId && isLastRetry) {
223
+ this.removeDiProbe(this.runningTestProbeId)
224
+ this.runningTestProbeId = null
225
+ }
248
226
  }
249
227
  })
250
228
 
@@ -271,7 +249,7 @@ class MochaPlugin extends CiPlugin {
271
249
  }
272
250
  })
273
251
 
274
- this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err }) => {
252
+ this.addSub('ci:mocha:test:retry', ({ isFirstAttempt, willBeRetried, err, test }) => {
275
253
  const store = storage.getStore()
276
254
  const span = store?.span
277
255
  if (span) {
@@ -294,10 +272,15 @@ class MochaPlugin extends CiPlugin {
294
272
  browserDriver: spanTags[TEST_BROWSER_DRIVER]
295
273
  }
296
274
  )
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)
275
+ if (isFirstAttempt && willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
276
+ const probeInformation = this.addDiProbe(err)
277
+ if (probeInformation) {
278
+ const { probeId, stackIndex } = probeInformation
279
+ this.runningTestProbeId = probeId
280
+ this.testErrorStackIndex = stackIndex
281
+ test._ddShouldWaitForHitProbe = true
282
+ // TODO: we're not waiting for setProbePromise to be resolved, so there might be race conditions
283
+ }
301
284
  }
302
285
 
303
286
  span.finish()
@@ -439,6 +422,7 @@ class MochaPlugin extends CiPlugin {
439
422
  extraTags[TEST_IS_NEW] = 'true'
440
423
  if (isEfdRetry) {
441
424
  extraTags[TEST_IS_RETRY] = 'true'
425
+ extraTags[TEST_RETRY_REASON] = 'efd'
442
426
  }
443
427
  }
444
428
 
@@ -15,7 +15,8 @@ const {
15
15
  TEST_IS_NEW,
16
16
  TEST_IS_RETRY,
17
17
  TEST_EARLY_FLAKE_ENABLED,
18
- TELEMETRY_TEST_SESSION
18
+ TELEMETRY_TEST_SESSION,
19
+ TEST_RETRY_REASON
19
20
  } = require('../../dd-trace/src/plugins/util/test')
20
21
  const { RESOURCE_NAME } = require('../../../ext/tags')
21
22
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -144,6 +145,7 @@ class PlaywrightPlugin extends CiPlugin {
144
145
  span.setTag(TEST_IS_NEW, 'true')
145
146
  if (isEfdRetry) {
146
147
  span.setTag(TEST_IS_RETRY, 'true')
148
+ span.setTag(TEST_RETRY_REASON, 'efd')
147
149
  }
148
150
  }
149
151
  if (isRetry) {
@@ -18,11 +18,7 @@ const {
18
18
  TEST_IS_NEW,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
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
21
+ TEST_RETRY_REASON
26
22
  } = require('../../dd-trace/src/plugins/util/test')
27
23
  const { COMPONENT } = require('../../dd-trace/src/constants')
28
24
  const {
@@ -36,8 +32,6 @@ const {
36
32
  // This is because there's some loss of resolution.
37
33
  const MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION = 5
38
34
 
39
- const debuggerParameterPerTest = new Map()
40
-
41
35
  class VitestPlugin extends CiPlugin {
42
36
  static get id () {
43
37
  return 'vitest'
@@ -67,7 +61,14 @@ class VitestPlugin extends CiPlugin {
67
61
  onDone(isFaulty)
68
62
  })
69
63
 
70
- this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry, isNew }) => {
64
+ this.addSub('ci:vitest:test:start', ({
65
+ testName,
66
+ testSuiteAbsolutePath,
67
+ isRetry,
68
+ isNew,
69
+ mightHitProbe,
70
+ isRetryReasonEfd
71
+ }) => {
71
72
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
72
73
  const store = storage.getStore()
73
74
 
@@ -80,6 +81,9 @@ class VitestPlugin extends CiPlugin {
80
81
  if (isNew) {
81
82
  extraTags[TEST_IS_NEW] = 'true'
82
83
  }
84
+ if (isRetryReasonEfd) {
85
+ extraTags[TEST_RETRY_REASON] = 'efd'
86
+ }
83
87
 
84
88
  const span = this.startTestSpan(
85
89
  testName,
@@ -88,27 +92,13 @@ class VitestPlugin extends CiPlugin {
88
92
  extraTags
89
93
  )
90
94
 
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
95
+ this.enter(span, store)
102
96
 
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)
97
+ // TODO: there might be multiple tests for which mightHitProbe is true, so activeTestSpan
98
+ // might be wrongly overwritten.
99
+ if (mightHitProbe) {
100
+ this.activeTestSpan = span
109
101
  }
110
-
111
- this.enter(span, store)
112
102
  })
113
103
 
114
104
  this.addSub('ci:vitest:test:finish-time', ({ status, task }) => {
@@ -137,15 +127,19 @@ class VitestPlugin extends CiPlugin {
137
127
  }
138
128
  })
139
129
 
140
- this.addSub('ci:vitest:test:error', ({ duration, error, willBeRetried, probe, isDiEnabled }) => {
130
+ this.addSub('ci:vitest:test:error', ({ duration, error, shouldSetProbe, promises }) => {
141
131
  const store = storage.getStore()
142
132
  const span = store?.span
143
133
 
144
134
  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)
135
+ if (shouldSetProbe && this.di) {
136
+ const probeInformation = this.addDiProbe(error)
137
+ if (probeInformation) {
138
+ const { probeId, stackIndex, setProbePromise } = probeInformation
139
+ this.runningTestProbeId = probeId
140
+ this.testErrorStackIndex = stackIndex
141
+ promises.setProbePromise = setProbePromise
142
+ }
149
143
  }
150
144
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
151
145
  hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
@@ -158,13 +152,13 @@ class VitestPlugin extends CiPlugin {
158
152
  if (duration) {
159
153
  span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
160
154
  } else {
161
- span.finish() // retries will not have a duration
155
+ span.finish() // `duration` is empty for retries, so we'll use clock time
162
156
  }
163
157
  finishAllTraceSpans(span)
164
158
  }
165
159
  })
166
160
 
167
- this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath }) => {
161
+ this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew }) => {
168
162
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
169
163
  const testSpan = this.startTestSpan(
170
164
  testName,
@@ -173,7 +167,8 @@ class VitestPlugin extends CiPlugin {
173
167
  {
174
168
  [TEST_SOURCE_FILE]: testSuite,
175
169
  [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
176
- [TEST_STATUS]: 'skip'
170
+ [TEST_STATUS]: 'skip',
171
+ ...(isNew ? { [TEST_IS_NEW]: 'true' } : {})
177
172
  }
178
173
  )
179
174
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
@@ -242,6 +237,9 @@ class VitestPlugin extends CiPlugin {
242
237
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
243
238
  // TODO: too frequent flush - find for method in worker to decrease frequency
244
239
  this.tracer._exporter.flush(onFinish)
240
+ if (this.runningTestProbeId) {
241
+ this.removeDiProbe(this.runningTestProbeId)
242
+ }
245
243
  })
246
244
 
247
245
  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 () {