dd-trace 5.101.0 → 5.102.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 (140) hide show
  1. package/package.json +9 -7
  2. package/packages/datadog-instrumentations/src/aerospike.js +2 -2
  3. package/packages/datadog-instrumentations/src/ai.js +8 -8
  4. package/packages/datadog-instrumentations/src/amqplib.js +6 -7
  5. package/packages/datadog-instrumentations/src/anthropic.js +10 -10
  6. package/packages/datadog-instrumentations/src/apollo-server-core.js +3 -3
  7. package/packages/datadog-instrumentations/src/apollo-server.js +5 -5
  8. package/packages/datadog-instrumentations/src/avsc.js +6 -6
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +151 -67
  10. package/packages/datadog-instrumentations/src/azure-durable-functions.js +8 -8
  11. package/packages/datadog-instrumentations/src/bluebird.js +2 -2
  12. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  13. package/packages/datadog-instrumentations/src/cassandra-driver.js +7 -7
  14. package/packages/datadog-instrumentations/src/child_process.js +12 -12
  15. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +9 -9
  16. package/packages/datadog-instrumentations/src/connect.js +7 -7
  17. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  18. package/packages/datadog-instrumentations/src/cookie.js +2 -2
  19. package/packages/datadog-instrumentations/src/couchbase.js +16 -30
  20. package/packages/datadog-instrumentations/src/crypto.js +4 -4
  21. package/packages/datadog-instrumentations/src/cucumber.js +77 -16
  22. package/packages/datadog-instrumentations/src/dns.js +0 -3
  23. package/packages/datadog-instrumentations/src/elasticsearch.js +8 -11
  24. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +6 -6
  25. package/packages/datadog-instrumentations/src/express-session.js +4 -4
  26. package/packages/datadog-instrumentations/src/express.js +10 -11
  27. package/packages/datadog-instrumentations/src/fastify.js +2 -2
  28. package/packages/datadog-instrumentations/src/fs.js +14 -14
  29. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +5 -7
  30. package/packages/datadog-instrumentations/src/google-genai.js +4 -4
  31. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  32. package/packages/datadog-instrumentations/src/hapi.js +2 -2
  33. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +8 -8
  34. package/packages/datadog-instrumentations/src/helpers/promise.js +2 -2
  35. package/packages/datadog-instrumentations/src/hono.js +2 -2
  36. package/packages/datadog-instrumentations/src/http/client.js +6 -6
  37. package/packages/datadog-instrumentations/src/http/server.js +9 -9
  38. package/packages/datadog-instrumentations/src/jest.js +31 -31
  39. package/packages/datadog-instrumentations/src/kafkajs.js +9 -9
  40. package/packages/datadog-instrumentations/src/knex.js +17 -17
  41. package/packages/datadog-instrumentations/src/koa.js +12 -12
  42. package/packages/datadog-instrumentations/src/ldapjs.js +5 -5
  43. package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
  44. package/packages/datadog-instrumentations/src/limitd-client.js +4 -4
  45. package/packages/datadog-instrumentations/src/lodash.js +4 -4
  46. package/packages/datadog-instrumentations/src/mariadb.js +13 -13
  47. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  48. package/packages/datadog-instrumentations/src/microgateway-core.js +2 -2
  49. package/packages/datadog-instrumentations/src/mocha/common.js +3 -3
  50. package/packages/datadog-instrumentations/src/mocha/main.js +12 -10
  51. package/packages/datadog-instrumentations/src/mocha/utils.js +133 -16
  52. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  53. package/packages/datadog-instrumentations/src/mongodb-core.js +9 -22
  54. package/packages/datadog-instrumentations/src/mongodb.js +5 -5
  55. package/packages/datadog-instrumentations/src/mongoose.js +21 -21
  56. package/packages/datadog-instrumentations/src/mquery.js +5 -5
  57. package/packages/datadog-instrumentations/src/multer.js +4 -4
  58. package/packages/datadog-instrumentations/src/mysql.js +16 -16
  59. package/packages/datadog-instrumentations/src/mysql2.js +4 -4
  60. package/packages/datadog-instrumentations/src/net.js +14 -8
  61. package/packages/datadog-instrumentations/src/nyc.js +5 -5
  62. package/packages/datadog-instrumentations/src/openai.js +19 -19
  63. package/packages/datadog-instrumentations/src/oracledb.js +6 -6
  64. package/packages/datadog-instrumentations/src/passport-utils.js +5 -5
  65. package/packages/datadog-instrumentations/src/pg.js +15 -15
  66. package/packages/datadog-instrumentations/src/pino.js +6 -10
  67. package/packages/datadog-instrumentations/src/playwright.js +20 -15
  68. package/packages/datadog-instrumentations/src/protobufjs.js +16 -16
  69. package/packages/datadog-instrumentations/src/redis.js +1 -2
  70. package/packages/datadog-instrumentations/src/restify.js +2 -2
  71. package/packages/datadog-instrumentations/src/router.js +12 -12
  72. package/packages/datadog-instrumentations/src/stripe.js +12 -12
  73. package/packages/datadog-instrumentations/src/vitest.js +107 -26
  74. package/packages/datadog-instrumentations/src/winston.js +4 -4
  75. package/packages/datadog-instrumentations/src/ws.js +7 -7
  76. package/packages/datadog-plugin-aws-sdk/src/base.js +52 -4
  77. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +19 -12
  78. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +45 -35
  79. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +33 -22
  80. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +12 -13
  81. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +73 -54
  82. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +19 -17
  83. package/packages/datadog-plugin-aws-sdk/src/util.js +22 -0
  84. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -6
  85. package/packages/datadog-plugin-cucumber/src/index.js +4 -0
  86. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -4
  87. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -5
  88. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +3 -1
  89. package/packages/datadog-plugin-http/src/client.js +1 -5
  90. package/packages/datadog-plugin-jest/src/util.js +1 -2
  91. package/packages/datadog-plugin-mocha/src/index.js +4 -0
  92. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -1
  93. package/packages/datadog-plugin-openai/src/tracing.js +12 -23
  94. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  95. package/packages/datadog-plugin-vitest/src/index.js +8 -1
  96. package/packages/datadog-shimmer/src/shimmer.js +7 -1
  97. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  98. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +81 -81
  99. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  100. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  101. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
  102. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -2
  103. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
  104. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -3
  105. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +83 -48
  106. package/packages/dd-trace/src/appsec/index.js +21 -24
  107. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  108. package/packages/dd-trace/src/appsec/rule_manager.js +4 -2
  109. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +31 -16
  110. package/packages/dd-trace/src/config/git_properties.js +2 -2
  111. package/packages/dd-trace/src/datastreams/index.js +2 -1
  112. package/packages/dd-trace/src/datastreams/processor.js +1 -2
  113. package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +1 -0
  114. package/packages/dd-trace/src/encode/0.4.js +757 -232
  115. package/packages/dd-trace/src/encode/0.5.js +13 -7
  116. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -2
  117. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +6 -3
  118. package/packages/dd-trace/src/llmobs/sdk.js +24 -26
  119. package/packages/dd-trace/src/llmobs/span_processor.js +25 -5
  120. package/packages/dd-trace/src/llmobs/util.js +1 -0
  121. package/packages/dd-trace/src/msgpack/chunk.js +6 -3
  122. package/packages/dd-trace/src/openfeature/noop.js +40 -36
  123. package/packages/dd-trace/src/openfeature/writers/exposures.js +33 -52
  124. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +1 -2
  125. package/packages/dd-trace/src/opentelemetry/tracer.js +0 -22
  126. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +2 -11
  127. package/packages/dd-trace/src/plugins/util/ci.js +1 -1
  128. package/packages/dd-trace/src/plugins/util/git-cache.js +3 -5
  129. package/packages/dd-trace/src/plugins/util/test.js +19 -7
  130. package/packages/dd-trace/src/plugins/util/url.js +1 -3
  131. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  132. package/packages/dd-trace/src/plugins/util/web.js +5 -7
  133. package/packages/dd-trace/src/profiling/profilers/events.js +3 -23
  134. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -5
  135. package/packages/dd-trace/src/runtime_metrics/index.js +2 -2
  136. package/packages/dd-trace/src/scope.js +3 -10
  137. package/packages/dd-trace/src/serverless.js +1 -4
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +7 -1
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +4 -0
  140. package/packages/dd-trace/src/tracer.js +7 -7
@@ -35,7 +35,7 @@ function wrapAddCommand (addCommand) {
35
35
  }
36
36
 
37
37
  function wrapCommandQueueClass (cls) {
38
- const ret = class RedisCommandQueue extends cls {
38
+ return class RedisCommandQueue extends cls {
39
39
  constructor (...args) {
40
40
  super(...args)
41
41
  let url = { host: 'localhost', port: 6379 }
@@ -50,7 +50,6 @@ function wrapCommandQueueClass (cls) {
50
50
  instanceInfo.set(this, { connectionName: createClientName, url })
51
51
  }
52
52
  }
53
- return ret
54
53
  }
55
54
 
56
55
  function wrapCreateClient (request) {
@@ -72,11 +72,11 @@ function wrapFn (fn) {
72
72
  }
73
73
 
74
74
  function wrapNext (req, next) {
75
- return shimmer.wrapFunction(next, next => function () {
75
+ return shimmer.wrapFunction(next, next => function (...args) {
76
76
  nextChannel.publish({ req })
77
77
  finishChannel.publish({ req })
78
78
 
79
- next.apply(this, arguments)
79
+ next.apply(this, args)
80
80
  })
81
81
  }
82
82
 
@@ -45,17 +45,17 @@ function createWrapRouterMethod (name, compile) {
45
45
  function wrapLayerHandle (layer, original) {
46
46
  original._name = original._name || layer.name
47
47
 
48
- return shimmer.wrapFunction(original, original => function () {
49
- if (!enterChannel.hasSubscribers) return original.apply(this, arguments)
48
+ return shimmer.wrapFunction(original, original => function (...args) {
49
+ if (!enterChannel.hasSubscribers) return original.apply(this, args)
50
50
 
51
51
  const matchers = getLayerMatchers(layer)
52
- const lastIndex = arguments.length - 1
52
+ const lastIndex = args.length - 1
53
53
  const name = original._name || original.name
54
- const req = arguments[arguments.length > 3 ? 1 : 0]
55
- const next = arguments[lastIndex]
54
+ const req = args[args.length > 3 ? 1 : 0]
55
+ const next = args[lastIndex]
56
56
 
57
57
  if (typeof next === 'function') {
58
- arguments[lastIndex] = wrapNext(req, next)
58
+ args[lastIndex] = wrapNext(req, next)
59
59
  }
60
60
 
61
61
  let route
@@ -77,7 +77,7 @@ function createWrapRouterMethod (name, compile) {
77
77
  enterChannel.publish({ name, req, route, layer })
78
78
 
79
79
  try {
80
- return original.apply(this, arguments)
80
+ return original.apply(this, args)
81
81
  } catch (error) {
82
82
  errorChannel.publish({ req, error })
83
83
  nextChannel.publish({ req })
@@ -251,8 +251,8 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => {
251
251
  const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
252
252
 
253
253
  const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) {
254
- return function wrappedMethod () {
255
- const router = originalRouter.apply(this, arguments)
254
+ return function wrappedMethod (...args) {
255
+ const router = originalRouter.apply(this, args)
256
256
 
257
257
  shimmer.wrap(router, 'handle', function wrapHandle (originalHandle) {
258
258
  return function wrappedHandle (req, res, next) {
@@ -310,8 +310,8 @@ addHook({
310
310
  })
311
311
 
312
312
  function wrapParam (original) {
313
- return function wrappedProcessParams () {
314
- arguments[1] = shimmer.wrapFunction(arguments[1], (originalFn) => {
313
+ return function wrappedProcessParams (...args) {
314
+ args[1] = shimmer.wrapFunction(args[1], (originalFn) => {
315
315
  return function wrappedFn (req, res) {
316
316
  if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) {
317
317
  visitedParams.add(req.params)
@@ -332,7 +332,7 @@ function wrapParam (original) {
332
332
  }
333
333
  })
334
334
 
335
- return original.apply(this, arguments)
335
+ return original.apply(this, args)
336
336
  }
337
337
  }
338
338
 
@@ -8,8 +8,8 @@ const paymentIntentCreateFinishCh = channel('datadog:stripe:paymentIntent:create
8
8
  const constructEventFinishCh = channel('datadog:stripe:constructEvent:finish')
9
9
 
10
10
  function wrapSessionCreate (create) {
11
- return function wrappedSessionCreate () {
12
- const promise = create.apply(this, arguments)
11
+ return function wrappedSessionCreate (...args) {
12
+ const promise = create.apply(this, args)
13
13
 
14
14
  if (!checkoutSessionCreateFinishCh.hasSubscribers) return promise
15
15
 
@@ -21,8 +21,8 @@ function wrapSessionCreate (create) {
21
21
  }
22
22
 
23
23
  function wrapPaymentIntentCreate (create) {
24
- return function wrappedPaymentIntentCreate () {
25
- const promise = create.apply(this, arguments)
24
+ return function wrappedPaymentIntentCreate (...args) {
25
+ const promise = create.apply(this, args)
26
26
 
27
27
  if (!paymentIntentCreateFinishCh.hasSubscribers) return promise
28
28
 
@@ -34,8 +34,8 @@ function wrapPaymentIntentCreate (create) {
34
34
  }
35
35
 
36
36
  function wrapConstructEvent (constructEvent) {
37
- return function wrappedConstructEvent () {
38
- const result = constructEvent.apply(this, arguments)
37
+ return function wrappedConstructEvent (...args) {
38
+ const result = constructEvent.apply(this, args)
39
39
 
40
40
  // no need to check for hasSubscribers,
41
41
  // if it's false, the publish function will be noop
@@ -46,8 +46,8 @@ function wrapConstructEvent (constructEvent) {
46
46
  }
47
47
 
48
48
  function wrapConstructEventAsync (constructEventAsync) {
49
- return function wrappedConstructEventAsync () {
50
- const promise = constructEventAsync.apply(this, arguments)
49
+ return function wrappedConstructEventAsync (...args) {
50
+ const promise = constructEventAsync.apply(this, args)
51
51
 
52
52
  if (!constructEventFinishCh.hasSubscribers) return promise
53
53
 
@@ -77,8 +77,8 @@ function instrumentStripeInstance (stripe) {
77
77
  // returns nothing; without 'new' it delegates to 'new Stripe(...)' and returns
78
78
  // that result. We need to instrument whichever object actually got populated
79
79
  function wrapLegacyStripe (Stripe) {
80
- return function wrappedStripe () {
81
- const result = Stripe.apply(this, arguments)
80
+ return function wrappedStripe (...args) {
81
+ const result = Stripe.apply(this, args)
82
82
  const stripe = this instanceof Stripe ? this : result
83
83
  instrumentStripeInstance(stripe)
84
84
  return stripe
@@ -88,8 +88,8 @@ function wrapLegacyStripe (Stripe) {
88
88
  // stripe >=22: the constructor is a factory that always returns a fresh Stripe
89
89
  // instance regardless of 'new', so we just instrument and forward the result
90
90
  function wrapStripe (Stripe) {
91
- return function wrappedStripe () {
92
- const stripe = Stripe.apply(this, arguments)
91
+ return function wrappedStripe (...args) {
92
+ const stripe = Stripe.apply(this, args)
93
93
  instrumentStripeInstance(stripe)
94
94
  return stripe
95
95
  }
@@ -4,6 +4,7 @@
4
4
  const realSetTimeout = setTimeout
5
5
 
6
6
  const path = require('node:path')
7
+ const { performance } = require('node:perf_hooks')
7
8
 
8
9
  const shimmer = require('../../datadog-shimmer')
9
10
  const log = require('../../dd-trace/src/log')
@@ -12,6 +13,8 @@ const {
12
13
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
13
14
  DYNAMIC_NAME_RE,
14
15
  getTestSuitePath,
16
+ getEfdRetryCount,
17
+ getMaxEfdRetryCount,
15
18
  recordAttemptToFixExecution,
16
19
  collectTestOptimizationSummariesFromTraces,
17
20
  logAttemptToFixTestExecution,
@@ -61,6 +64,10 @@ const disabledTasks = new WeakSet()
61
64
  const quarantinedTasks = new WeakSet()
62
65
  const attemptToFixTasks = new WeakSet()
63
66
  const modifiedTasks = new WeakSet()
67
+ const efdDeterminedRetries = new WeakMap()
68
+ const efdSlowAbortedTasks = new WeakSet()
69
+ const efdExecutionStartByTask = new WeakMap()
70
+ const efdSkippedRetryResults = new WeakMap()
64
71
  const attemptToFixExecutions = new Map()
65
72
  const loggedAttemptToFixTests = new Set()
66
73
  let isRetryReasonEfd = false
@@ -71,6 +78,7 @@ let isFlakyTestRetriesEnabled = false
71
78
  let flakyTestRetriesCount = 0
72
79
  let isEarlyFlakeDetectionEnabled = false
73
80
  let earlyFlakeDetectionNumRetries = 0
81
+ let earlyFlakeDetectionSlowTestRetries = {}
74
82
  let isEarlyFlakeDetectionFaulty = false
75
83
  let isKnownTestsEnabled = false
76
84
  let isTestManagementTestsEnabled = false
@@ -87,6 +95,13 @@ let vitestPool = null
87
95
 
88
96
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400
89
97
 
98
+ function getConfiguredEfdRetryCount (slowTestRetries, fallbackRetryCount) {
99
+ if (!slowTestRetries || !Object.keys(slowTestRetries).length) {
100
+ return fallbackRetryCount
101
+ }
102
+ return getMaxEfdRetryCount(slowTestRetries)
103
+ }
104
+
90
105
  function getTestCommand () {
91
106
  return `vitest ${process.argv.slice(2).join(' ')}`
92
107
  }
@@ -110,6 +125,7 @@ function getProvidedContext () {
110
125
  _ddIsDiEnabled,
111
126
  _ddKnownTests: knownTests,
112
127
  _ddEarlyFlakeDetectionNumRetries: numRepeats,
128
+ _ddEarlyFlakeDetectionSlowTestRetries: slowTestRetries,
113
129
  _ddIsKnownTestsEnabled: isKnownTestsEnabled,
114
130
  _ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
115
131
  _ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
@@ -125,6 +141,7 @@ function getProvidedContext () {
125
141
  isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
126
142
  knownTests,
127
143
  numRepeats,
144
+ slowTestRetries: slowTestRetries ?? {},
128
145
  isKnownTestsEnabled,
129
146
  isTestManagementTestsEnabled,
130
147
  testManagementAttemptToFixRetries,
@@ -141,6 +158,7 @@ function getProvidedContext () {
141
158
  isEarlyFlakeDetectionEnabled: false,
142
159
  knownTests: {},
143
160
  numRepeats: 0,
161
+ slowTestRetries: {},
144
162
  isKnownTestsEnabled: false,
145
163
  isTestManagementTestsEnabled: false,
146
164
  testManagementAttemptToFixRetries: 0,
@@ -219,12 +237,8 @@ function getSessionStatus (state) {
219
237
 
220
238
  // From https://github.com/vitest-dev/vitest/blob/51c04e2f44d91322b334f8ccbcdb368facc3f8ec/packages/runner/src/run.ts#L243-L250
221
239
  function getVitestTestStatus (test, retryCount) {
222
- if (test.result.state !== 'fail') {
223
- if (!test.repeats) {
224
- return 'pass'
225
- } else if (test.repeats && (test.retry ?? 0) === retryCount) {
226
- return 'pass'
227
- }
240
+ if (test.result.state !== 'fail' && (!test.repeats || (test.retry ?? 0) === retryCount)) {
241
+ return 'pass'
228
242
  }
229
243
  return 'fail'
230
244
  }
@@ -291,8 +305,8 @@ function recordFinalAttemptToFixExecution (task, status, providedContext) {
291
305
  * @returns {Function}
292
306
  */
293
307
  function wrapTestScopedFn (task, fn) {
294
- return shimmer.wrapFunction(fn, fn => function () {
295
- return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
308
+ return shimmer.wrapFunction(fn, fn => function (...args) {
309
+ return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
296
310
  })
297
311
  }
298
312
 
@@ -331,6 +345,7 @@ function getSortWrapper (sort, frameworkVersion) {
331
345
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
332
346
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
333
347
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
348
+ earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
334
349
  isDiEnabled = libraryConfig.isDiEnabled
335
350
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
336
351
  isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
@@ -389,7 +404,9 @@ function getSortWrapper (sort, frameworkVersion) {
389
404
  workspaceProject._provided._ddIsKnownTestsEnabled = isKnownTestsEnabled
390
405
  workspaceProject._provided._ddKnownTests = knownTests
391
406
  workspaceProject._provided._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
392
- workspaceProject._provided._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
407
+ workspaceProject._provided._ddEarlyFlakeDetectionNumRetries =
408
+ getConfiguredEfdRetryCount(earlyFlakeDetectionSlowTestRetries, earlyFlakeDetectionNumRetries)
409
+ workspaceProject._provided._ddEarlyFlakeDetectionSlowTestRetries = earlyFlakeDetectionSlowTestRetries
393
410
  } catch {
394
411
  log.warn('Could not send known tests to workers so Early Flake Detection will not work.')
395
412
  }
@@ -527,13 +544,13 @@ function getFinishWrapper (exitOrClose) {
527
544
 
528
545
  function getCliOrStartVitestWrapper (frameworkVersion) {
529
546
  return function (oldCliOrStartVitest) {
530
- return function () {
547
+ return function (...args) {
531
548
  if (!testSessionStartCh.hasSubscribers || isSessionStarted) {
532
- return oldCliOrStartVitest.apply(this, arguments)
549
+ return oldCliOrStartVitest.apply(this, args)
533
550
  }
534
551
  isSessionStarted = true
535
552
  testSessionStartCh.publish({ command: getTestCommand(), frameworkVersion })
536
- return oldCliOrStartVitest.apply(this, arguments)
553
+ return oldCliOrStartVitest.apply(this, args)
537
554
  }
538
555
  }
539
556
  }
@@ -644,11 +661,11 @@ function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
644
661
  const forksPoolWorker = getForksPoolWorkerExport(cliApiPackage)
645
662
  if (forksPoolWorker) {
646
663
  // function is async
647
- shimmer.wrap(forksPoolWorker.value.prototype, 'start', start => function () {
664
+ shimmer.wrap(forksPoolWorker.value.prototype, 'start', start => function (...args) {
648
665
  vitestPool = 'child_process'
649
666
  this.env.DD_VITEST_WORKER = '1'
650
667
 
651
- return start.apply(this, arguments)
668
+ return start.apply(this, args)
652
669
  })
653
670
  shimmer.wrap(forksPoolWorker.value.prototype, 'on', getWrappedOn)
654
671
  }
@@ -656,10 +673,10 @@ function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
656
673
  const threadsPoolWorker = getThreadsPoolWorkerExport(cliApiPackage)
657
674
  if (threadsPoolWorker) {
658
675
  // function is async
659
- shimmer.wrap(threadsPoolWorker.value.prototype, 'start', start => function () {
676
+ shimmer.wrap(threadsPoolWorker.value.prototype, 'start', start => function (...args) {
660
677
  vitestPool = 'worker_threads'
661
678
  this.env.DD_VITEST_WORKER = '1'
662
- return start.apply(this, arguments)
679
+ return start.apply(this, args)
663
680
  })
664
681
  shimmer.wrap(threadsPoolWorker.value.prototype, 'on', getWrappedOn)
665
682
  }
@@ -721,7 +738,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
721
738
  onDone: (isImpacted) => {
722
739
  if (isImpacted) {
723
740
  if (isEarlyFlakeDetectionEnabled) {
724
- isRetryReasonEfd = task.repeats !== numRepeats
741
+ isRetryReasonEfd = true
725
742
  task.repeats = numRepeats
726
743
  }
727
744
  modifiedTasks.add(task)
@@ -739,7 +756,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
739
756
  onDone: (isNew) => {
740
757
  if (isNew && !attemptToFixTasks.has(task)) {
741
758
  if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
742
- isRetryReasonEfd = task.repeats !== numRepeats
759
+ isRetryReasonEfd = true
743
760
  task.repeats = numRepeats
744
761
  }
745
762
  newTasks.add(task)
@@ -811,6 +828,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
811
828
  isTestManagementTestsEnabled,
812
829
  testManagementTests,
813
830
  isFlakyTestRetriesEnabled,
831
+ slowTestRetries,
814
832
  } = getProvidedContext()
815
833
 
816
834
  if (isKnownTestsEnabled) {
@@ -832,6 +850,39 @@ function wrapVitestTestRunner (VitestTestRunner) {
832
850
  }
833
851
 
834
852
  const { retry: numAttempt, repeats: numRepetition } = retryInfo
853
+ const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
854
+
855
+ if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
856
+ const previousExecutionStart = efdExecutionStartByTask.get(task)
857
+ const duration = previousExecutionStart === undefined
858
+ ? task.result?.duration ?? 0
859
+ : performance.now() - previousExecutionStart
860
+ const retryCount = getEfdRetryCount(duration, slowTestRetries)
861
+ efdDeterminedRetries.set(task, retryCount)
862
+ task.repeats = retryCount
863
+ if (retryCount === 0) {
864
+ efdSlowAbortedTasks.add(task)
865
+ }
866
+ }
867
+
868
+ const efdRetryCount = efdDeterminedRetries.get(task)
869
+ if (isEfdManagedTask && efdRetryCount !== undefined && numRepetition > efdRetryCount) {
870
+ if (task.result) {
871
+ efdSkippedRetryResults.set(task, {
872
+ ...task.result,
873
+ errors: task.result.errors?.slice(),
874
+ })
875
+ }
876
+ if (vitestSetFn) {
877
+ const noop = function () {}
878
+ noop.__ddTraceWrapped = true
879
+ vitestSetFn(task, noop)
880
+ }
881
+ return onBeforeTryTask.apply(this, arguments)
882
+ }
883
+ if (isEfdManagedTask) {
884
+ efdExecutionStartByTask.set(task, performance.now())
885
+ }
835
886
 
836
887
  // We finish the previous test here because we know it has failed already
837
888
  if (numAttempt > 0) {
@@ -963,8 +1014,8 @@ function wrapVitestTestRunner (VitestTestRunner) {
963
1014
  for (let i = 0; i < hookArray.length; i++) {
964
1015
  const currentFn = hookArray[i]
965
1016
  const originalFn = originalHookFns.get(currentFn) || currentFn
966
- const wrappedFn = shimmer.wrapFunction(originalFn, fn => function () {
967
- const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
1017
+ const wrappedFn = shimmer.wrapFunction(originalFn, fn => function (...args) {
1018
+ const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
968
1019
 
969
1020
  if (hookType === 'beforeEach') {
970
1021
  return wrapBeforeEachCleanupResult(task, result)
@@ -985,23 +1036,50 @@ function wrapVitestTestRunner (VitestTestRunner) {
985
1036
 
986
1037
  // test finish (only passed tests)
987
1038
  shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
988
- async function (task, { retry: retryCount }) {
1039
+ async function (task, retryInfo) {
989
1040
  if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
990
1041
  return onAfterTryTask.apply(this, arguments)
991
1042
  }
992
1043
  const result = await onAfterTryTask.apply(this, arguments)
993
1044
 
994
- const { testManagementAttemptToFixRetries } = getProvidedContext()
1045
+ const {
1046
+ isEarlyFlakeDetectionEnabled,
1047
+ testManagementAttemptToFixRetries,
1048
+ slowTestRetries,
1049
+ } = getProvidedContext()
995
1050
 
996
- const status = getVitestTestStatus(task, retryCount)
1051
+ const status = getVitestTestStatus(task, retryInfo.retry)
997
1052
  const ctx = taskToCtx.get(task)
998
1053
 
999
1054
  const { isDiEnabled } = getProvidedContext()
1000
1055
 
1001
- if (isDiEnabled && retryCount > 1) {
1056
+ if (efdSkippedRetryResults.has(task)) {
1057
+ task.result = efdSkippedRetryResults.get(task)
1058
+ efdSkippedRetryResults.delete(task)
1059
+ return result
1060
+ }
1061
+
1062
+ if (isDiEnabled && retryInfo.retry > 1) {
1002
1063
  await waitForHitProbe()
1003
1064
  }
1004
1065
 
1066
+ if (
1067
+ isEarlyFlakeDetectionEnabled &&
1068
+ (retryInfo.repeats ?? 0) === 0 &&
1069
+ taskToStatuses.has(task) &&
1070
+ !attemptToFixTasks.has(task) &&
1071
+ !efdDeterminedRetries.has(task)
1072
+ ) {
1073
+ const executionStart = efdExecutionStartByTask.get(task)
1074
+ const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
1075
+ const retryCount = getEfdRetryCount(duration, slowTestRetries)
1076
+ efdDeterminedRetries.set(task, retryCount)
1077
+ task.repeats = retryCount
1078
+ if (retryCount === 0) {
1079
+ efdSlowAbortedTasks.add(task)
1080
+ }
1081
+ }
1082
+
1005
1083
  let attemptToFixPassed = false
1006
1084
  let attemptToFixFailed = false
1007
1085
  if (attemptToFixTasks.has(task)) {
@@ -1234,6 +1312,7 @@ addHook({
1234
1312
  testPassCh.publish({
1235
1313
  task,
1236
1314
  finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
1315
+ earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
1237
1316
  ...testCtx.currentStore,
1238
1317
  })
1239
1318
  }
@@ -1255,8 +1334,9 @@ addHook({
1255
1334
  providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
1256
1335
  if (isEfdRetry) {
1257
1336
  const statuses = taskToStatuses.get(task)
1258
- // statuses only includes repetitions (not the initial run), so we check against numRepeats (not +1)
1259
- if (statuses && statuses.length === providedContext.numRepeats &&
1337
+ const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
1338
+ // statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
1339
+ if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
1260
1340
  statuses.every(status => status === 'fail')) {
1261
1341
  hasFailedAllRetries = true
1262
1342
  }
@@ -1297,6 +1377,7 @@ addHook({
1297
1377
  hasFailedAllRetries,
1298
1378
  attemptToFixFailed,
1299
1379
  finalStatus,
1380
+ earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
1300
1381
  ...testCtx.currentStore,
1301
1382
  })
1302
1383
  }
@@ -33,8 +33,8 @@ addHook({ name: 'winston', file: 'lib/winston/logger.js', versions: ['>=3'] }, L
33
33
  }
34
34
  })
35
35
 
36
- shimmer.wrap(Logger.prototype, 'configure', configure => function () {
37
- const configureResponse = configure.apply(this, arguments)
36
+ shimmer.wrap(Logger.prototype, 'configure', configure => function (...args) {
37
+ const configureResponse = configure.apply(this, args)
38
38
  // After the original `configure`, because it resets transports
39
39
  if (addTransport.hasSubscribers) {
40
40
  addTransport.publish(this)
@@ -55,8 +55,8 @@ addHook({ name: 'winston', file: 'lib/winston/logger.js', versions: ['1', '2'] }
55
55
  })
56
56
 
57
57
  function wrapMethod (method, logCh) {
58
- return function methodWithTrace () {
59
- const result = method.apply(this, arguments)
58
+ return function methodWithTrace (...args) {
59
+ const result = method.apply(this, args)
60
60
 
61
61
  if (logCh.hasSubscribers) {
62
62
  for (const name in this.transports) {
@@ -62,24 +62,24 @@ const setSocketCh = channel('tracing:ws:server:connect:setSocket')
62
62
  let kWebSocketSymbol
63
63
 
64
64
  function wrapHandleUpgrade (handleUpgrade) {
65
- return function () {
66
- const [req, socket, , cb] = arguments
65
+ return function (...args) {
66
+ const [req, socket, , cb] = args
67
67
  if (!serverCh.start.hasSubscribers || typeof cb !== 'function') {
68
- return handleUpgrade.apply(this, arguments)
68
+ return handleUpgrade.apply(this, args)
69
69
  }
70
70
 
71
71
  const ctx = { req, socket }
72
72
 
73
- arguments[3] = function () {
73
+ args[3] = function (...args) {
74
74
  return serverCh.asyncStart.runStores(ctx, () => {
75
75
  try {
76
- return cb.apply(this, arguments)
76
+ return cb.apply(this, args)
77
77
  } finally {
78
78
  serverCh.asyncEnd.publish(ctx)
79
79
  }
80
- }, this, ...arguments)
80
+ }, this, ...args)
81
81
  }
82
- return serverCh.traceSync(handleUpgrade, ctx, this, ...arguments)
82
+ return serverCh.traceSync(handleUpgrade, ctx, this, ...args)
83
83
  }
84
84
  }
85
85
 
@@ -8,10 +8,51 @@ const { tagsFromRequest, tagsFromResponse } = require('../../dd-trace/src/payloa
8
8
  const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
9
9
  const { IS_SERVERLESS } = require('../../dd-trace/src/serverless')
10
10
 
11
+ const RESPONSE_SKIP_KEYS = new Set(['request', 'requestId', 'error', '$metadata'])
12
+
11
13
  class BaseAwsSdkPlugin extends ClientPlugin {
12
14
  static id = 'aws'
13
15
  static isPayloadReporter = false
14
16
 
17
+ /**
18
+ * Append `"<key>": <JSON.stringify(value)>` to a JSON-encoded object
19
+ * payload without re-parsing when possible.
20
+ *
21
+ * Fast path: `payload` is `{}` (returns `{"<key>":<json>}`) or ends with
22
+ * `}` preceded by a non-whitespace, non-`{` byte and does not contain
23
+ * `"<key>"` anywhere. The new field is spliced in before the trailing
24
+ * brace.
25
+ *
26
+ * Slow path falls back to `JSON.parse` + assign + `JSON.stringify` so the
27
+ * result still matches the previous round-trip when the payload has
28
+ * whitespace before the trailing `}`, is not a JSON object, or already
29
+ * contains `key`. The slow path replaces an existing `key` rather than
30
+ * merging — callers that need to preserve nested fields under `key` must
31
+ * read and merge before calling.
32
+ *
33
+ * @param {string} payload
34
+ * @param {string} key Top-level key to insert. Must be a simple
35
+ * identifier that does not need JSON escaping.
36
+ * @param {object} value Value to inject; will be `JSON.stringify`'d.
37
+ * @returns {string}
38
+ */
39
+ static injectFieldIntoJsonObject (payload, key, value) {
40
+ const last = payload.length - 1
41
+ if (last >= 1 && payload[last] === '}') {
42
+ if (last === 1) {
43
+ return `{"${key}":${JSON.stringify(value)}}`
44
+ }
45
+ const before = payload.charCodeAt(last - 1)
46
+ const isWhitespace = before === 0x20 || before === 0x09 || before === 0x0A || before === 0x0D
47
+ if (!isWhitespace && before !== 0x7B && !payload.includes(`"${key}"`)) {
48
+ return `${payload.slice(0, last)},"${key}":${JSON.stringify(value)}}`
49
+ }
50
+ }
51
+ const obj = JSON.parse(payload)
52
+ obj[key] = value
53
+ return JSON.stringify(obj)
54
+ }
55
+
15
56
  get serviceIdentifier () {
16
57
  const id = this.constructor.id.toLowerCase()
17
58
  Object.defineProperty(this, 'serviceIdentifier', {
@@ -224,12 +265,19 @@ class BaseAwsSdkPlugin extends ClientPlugin {
224
265
  }
225
266
 
226
267
  extractResponseBody (response) {
227
- if (response.hasOwnProperty('data')) {
268
+ if (Object.hasOwn(response, 'data')) {
228
269
  return response.data
229
270
  }
230
- return Object.fromEntries(
231
- Object.entries(response).filter(([key]) => !['request', 'requestId', 'error', '$metadata'].includes(key))
232
- )
271
+ // `{ ...response }` followed by `delete body.X` allocates a copy and then
272
+ // pushes the copy into V8 dictionary mode for every SDK response. Filter
273
+ // on build instead -- ~2.3x faster on the typical 4-of-8-keys shape.
274
+ const body = {}
275
+ for (const key of Object.keys(response)) {
276
+ if (!RESPONSE_SKIP_KEYS.has(key)) {
277
+ body[key] = response[key]
278
+ }
279
+ }
280
+ return body
233
281
  }
234
282
 
235
283
  generateTags () {
@@ -34,20 +34,27 @@ class EventBridge extends BaseAwsSdkPlugin {
34
34
  request.params.Entries &&
35
35
  request.params.Entries.length > 0 &&
36
36
  request.params.Entries[0].Detail) {
37
+ const injected = {}
38
+ this.tracer.inject(span, 'text_map', injected)
39
+
40
+ // Only `injectFieldIntoJsonObject` can throw (the slow path
41
+ // `JSON.parse` for non-`{...}` payloads). Tighten the catch around
42
+ // it so the rest of the body stays in V8's optimisable surface.
43
+ let finalData
37
44
  try {
38
- const details = JSON.parse(request.params.Entries[0].Detail)
39
- details._datadog = {}
40
- this.tracer.inject(span, 'text_map', details._datadog)
41
- const finalData = JSON.stringify(details)
42
- const byteSize = Buffer.byteLength(finalData)
43
- if (byteSize >= (1024 * 256)) {
44
- log.info('Payload size too large to pass context')
45
- return
46
- }
47
- request.params.Entries[0].Detail = finalData
48
- } catch (e) {
49
- log.error('EventBridge error injecting request', e)
45
+ finalData = BaseAwsSdkPlugin.injectFieldIntoJsonObject(
46
+ request.params.Entries[0].Detail, '_datadog', injected
47
+ )
48
+ } catch (error) {
49
+ log.error('EventBridge error injecting request', error)
50
+ return
51
+ }
52
+
53
+ if (Buffer.byteLength(finalData) >= 1024 * 256) {
54
+ log.info('Payload size too large to pass context')
55
+ return
50
56
  }
57
+ request.params.Entries[0].Detail = finalData
51
58
  }
52
59
  }
53
60
  }