dd-trace 5.99.1 → 5.101.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 (101) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +14 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  5. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  6. package/packages/datadog-instrumentations/src/express.js +3 -2
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/hono.js +15 -4
  9. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  10. package/packages/datadog-instrumentations/src/jest.js +146 -90
  11. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  12. package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
  15. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  16. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  17. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  18. package/packages/datadog-instrumentations/src/router.js +53 -33
  19. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  22. package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
  23. package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
  25. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  26. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  27. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  28. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  32. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  33. package/packages/datadog-plugin-router/src/index.js +13 -0
  34. package/packages/dd-trace/index.js +4 -3
  35. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  36. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  37. package/packages/dd-trace/src/baggage.js +10 -0
  38. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  39. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  40. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  41. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  42. package/packages/dd-trace/src/config/index.js +7 -60
  43. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  45. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  46. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  47. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  48. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  49. package/packages/dd-trace/src/datastreams/size.js +6 -2
  50. package/packages/dd-trace/src/debugger/config.js +6 -3
  51. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  52. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  53. package/packages/dd-trace/src/dogstatsd.js +10 -7
  54. package/packages/dd-trace/src/encode/0.4.js +3 -3
  55. package/packages/dd-trace/src/encode/0.5.js +2 -2
  56. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  57. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  58. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  59. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  60. package/packages/dd-trace/src/git_metadata.js +66 -0
  61. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  62. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  63. package/packages/dd-trace/src/id.js +15 -26
  64. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  65. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  66. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  68. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  69. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  70. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  71. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  72. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  73. package/packages/dd-trace/src/llmobs/util.js +80 -5
  74. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  75. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  76. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
  78. package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
  79. package/packages/dd-trace/src/opentelemetry/span.js +42 -108
  80. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  81. package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
  82. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
  83. package/packages/dd-trace/src/opentracing/span.js +58 -49
  84. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  85. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  86. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  87. package/packages/dd-trace/src/priority_sampler.js +6 -4
  88. package/packages/dd-trace/src/profiling/config.js +5 -4
  89. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  90. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  91. package/packages/dd-trace/src/proxy.js +3 -3
  92. package/packages/dd-trace/src/remote_config/index.js +5 -3
  93. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  94. package/packages/dd-trace/src/span_format.js +52 -5
  95. package/packages/dd-trace/src/span_processor.js +1 -5
  96. package/packages/dd-trace/src/spanleak.js +0 -1
  97. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  98. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  99. package/packages/dd-trace/src/util.js +17 -0
  100. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  101. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
4
- const pathToRegExp = require('../../../vendor/dist/path-to-regexp')
5
4
  const shimmer = require('../../datadog-shimmer')
6
5
  const { addHook, channel } = require('./helpers/instrument')
6
+ const { getCompileToRegexp } = require('./path-to-regexp')
7
7
 
8
8
  const {
9
9
  getRouterMountPaths,
@@ -19,15 +19,22 @@ const {
19
19
  } = require('./helpers/router-helper')
20
20
 
21
21
  function isFastStar (layer, matchers) {
22
- return layer.regexp?.fast_star ?? matchers.some(matcher => matcher.path === '*')
22
+ return layer.regexp?.fast_star ?? matchers.hasStarPath
23
23
  }
24
24
 
25
25
  function isFastSlash (layer, matchers) {
26
- return layer.regexp?.fast_slash ?? matchers.some(matcher => matcher.path === '/')
26
+ return layer.regexp?.fast_slash ?? matchers.hasSlashPath
27
27
  }
28
28
 
29
29
  // TODO: Move this function to a shared file between Express and Router
30
- function createWrapRouterMethod (name) {
30
+ /**
31
+ * @param {string} name Channel namespace (`apm:<name>:middleware:*`).
32
+ * @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
33
+ * Host-resolved path-to-regexp compile adapter, or undefined when the host
34
+ * instance ships no path-to-regexp. Captured here so each express/router
35
+ * instance keeps the dialect it actually loaded.
36
+ */
37
+ function createWrapRouterMethod (name, compile) {
31
38
  const enterChannel = channel(`apm:${name}:middleware:enter`)
32
39
  const exitChannel = channel(`apm:${name}:middleware:exit`)
33
40
  const finishChannel = channel(`apm:${name}:middleware:finish`)
@@ -35,8 +42,6 @@ function createWrapRouterMethod (name) {
35
42
  const nextChannel = channel(`apm:${name}:middleware:next`)
36
43
  const routeAddedChannel = channel(`apm:${name}:route:added`)
37
44
 
38
- const regexpCache = Object.create(null)
39
-
40
45
  function wrapLayerHandle (layer, original) {
41
46
  original._name = original._name || layer.name
42
47
 
@@ -55,13 +60,16 @@ function createWrapRouterMethod (name) {
55
60
 
56
61
  let route
57
62
 
58
- if (matchers) {
59
- // Try to guess which path actually matched
60
- for (const matcher of matchers) {
61
- if (matcher.test(layer)) {
62
- route = matcher.path
63
-
64
- break
63
+ if (matchers?.length && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
64
+ if (matchers.length === 1) {
65
+ // The host already matched this layer; the lone pattern is the route.
66
+ route = matchers[0].path
67
+ } else {
68
+ for (const matcher of matchers) {
69
+ if (matcher.regex?.test(layer.path)) {
70
+ route = matcher.path
71
+ break
72
+ }
65
73
  }
66
74
  }
67
75
  }
@@ -124,25 +132,35 @@ function createWrapRouterMethod (name) {
124
132
  return []
125
133
  }
126
134
 
127
- return arg.map(pattern => ({
128
- path: pattern instanceof RegExp ? `(${pattern})` : pattern,
129
- test: layer => {
130
- const matchers = getLayerMatchers(layer)
131
- return !isFastStar(layer, matchers) &&
132
- !isFastSlash(layer, matchers) &&
133
- cachedPathToRegExp(pattern).test(layer.path)
134
- },
135
- }))
136
- }
137
-
138
- function cachedPathToRegExp (pattern) {
139
- const maybeCached = regexpCache[pattern]
140
- if (maybeCached) {
141
- return maybeCached
135
+ if (arg.length === 1) {
136
+ const pattern = arg[0]
137
+ const path = pattern instanceof RegExp ? `(${pattern})` : pattern
138
+ const matchers = [{ path }]
139
+ matchers.hasStarPath = path === '*'
140
+ matchers.hasSlashPath = path === '/'
141
+ return matchers
142
142
  }
143
- const regexp = pathToRegExp(pattern)
144
- regexpCache[pattern] = regexp
145
- return regexp
143
+
144
+ // hasStarPath/hasSlashPath cache the lookups isFastStar/isFastSlash
145
+ // would otherwise re-run on every request.
146
+ let hasStarPath = false
147
+ let hasSlashPath = false
148
+ const matchers = arg.map(pattern => {
149
+ const isRegExp = pattern instanceof RegExp
150
+ const path = isRegExp ? `(${pattern})` : pattern
151
+ if (path === '*') {
152
+ hasStarPath = true
153
+ } else if (path === '/') {
154
+ hasSlashPath = true
155
+ }
156
+ return {
157
+ path,
158
+ regex: isRegExp ? pattern : compile?.(pattern),
159
+ }
160
+ })
161
+ matchers.hasStarPath = hasStarPath
162
+ matchers.hasSlashPath = hasSlashPath
163
+ return matchers
146
164
  }
147
165
 
148
166
  function wrapMethod (original) {
@@ -218,9 +236,9 @@ function createWrapRouterMethod (name) {
218
236
  return wrapMethod
219
237
  }
220
238
 
221
- const wrapRouterMethod = createWrapRouterMethod('router')
222
-
223
239
  addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
240
+ const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
241
+
224
242
  shimmer.wrap(Router.prototype, 'use', wrapRouterMethod)
225
243
  shimmer.wrap(Router.prototype, 'route', wrapRouterMethod)
226
244
 
@@ -230,6 +248,8 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
230
248
  const queryParserReadCh = channel('datadog:query:read:finish')
231
249
 
232
250
  addHook({ name: 'router', versions: ['>=2'] }, Router => {
251
+ const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
252
+
233
253
  const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) {
234
254
  return function wrappedMethod () {
235
255
  const router = originalRouter.apply(this, arguments)
@@ -11,8 +11,11 @@ const {
11
11
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
12
12
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
13
13
  DYNAMIC_NAME_RE,
14
- collectDynamicNamesFromTraces,
15
- logDynamicNamesWarning,
14
+ getTestSuitePath,
15
+ recordAttemptToFixExecution,
16
+ collectTestOptimizationSummariesFromTraces,
17
+ logAttemptToFixTestExecution,
18
+ logTestOptimizationSummary,
16
19
  } = require('../../dd-trace/src/plugins/util/test')
17
20
  const { addHook, channel } = require('./helpers/instrument')
18
21
 
@@ -49,6 +52,7 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
49
52
 
50
53
  const taskToCtx = new WeakMap()
51
54
  const taskToStatuses = new WeakMap()
55
+ const attemptToFixTaskToStatuses = new WeakMap()
52
56
  const originalHookFns = new WeakMap()
53
57
  const newTasks = new WeakSet()
54
58
  const dynamicNameTasks = new WeakSet()
@@ -57,6 +61,8 @@ const disabledTasks = new WeakSet()
57
61
  const quarantinedTasks = new WeakSet()
58
62
  const attemptToFixTasks = new WeakSet()
59
63
  const modifiedTasks = new WeakSet()
64
+ const attemptToFixExecutions = new Map()
65
+ const loggedAttemptToFixTests = new Set()
60
66
  let isRetryReasonEfd = false
61
67
  let isRetryReasonAttemptToFix = false
62
68
  const switchedStatuses = new WeakSet()
@@ -255,6 +261,29 @@ function getTestName (task) {
255
261
  return testName
256
262
  }
257
263
 
264
+ function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
265
+ if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
266
+ return testCtx.status
267
+ }
268
+
269
+ return state === 'fail' ? 'fail' : 'pass'
270
+ }
271
+
272
+ function recordFinalAttemptToFixExecution (task, status, providedContext) {
273
+ const statuses = attemptToFixTaskToStatuses.get(task)
274
+ if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
275
+ statuses.push(status)
276
+ }
277
+
278
+ recordAttemptToFixExecution(attemptToFixExecutions, {
279
+ testSuite: getTestSuitePath(task.file.filepath, process.cwd()),
280
+ testName: getTestName(task),
281
+ status,
282
+ isDisabled: disabledTasks.has(task),
283
+ isQuarantined: quarantinedTasks.has(task),
284
+ })
285
+ }
286
+
258
287
  /**
259
288
  * Wraps a function so it runs inside the current test span context.
260
289
  * @param {object} task
@@ -480,7 +509,8 @@ function getFinishWrapper (exitOrClose) {
480
509
  onFinish,
481
510
  })
482
511
 
483
- logDynamicNamesWarning(newTestsWithDynamicNames)
512
+ logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
513
+ loggedAttemptToFixTests.clear()
484
514
 
485
515
  await flushPromise
486
516
 
@@ -542,7 +572,10 @@ function threadHandler (thread) {
542
572
  workerProcess.on('message', (message) => {
543
573
  if (message.__tinypool_worker_message__ && message.data) {
544
574
  if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
545
- collectDynamicNamesFromTraces(message.data, newTestsWithDynamicNames)
575
+ collectTestOptimizationSummariesFromTraces(message.data, {
576
+ newTestsWithDynamicNames,
577
+ attemptToFixExecutions,
578
+ })
546
579
  workerReportTraceCh.publish(message.data)
547
580
  } else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
548
581
  workerReportLogsCh.publish(message.data)
@@ -584,7 +617,10 @@ function getWrappedOn (on) {
584
617
  if (message.type !== 'Buffer' && Array.isArray(message)) {
585
618
  const [interprocessCode, data] = message
586
619
  if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
587
- collectDynamicNamesFromTraces(data, newTestsWithDynamicNames)
620
+ collectTestOptimizationSummariesFromTraces(data, {
621
+ newTestsWithDynamicNames,
622
+ attemptToFixExecutions,
623
+ })
588
624
  workerReportTraceCh.publish(data)
589
625
  } else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
590
626
  workerReportLogsCh.publish(data)
@@ -658,7 +694,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
658
694
  isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
659
695
  task.repeats = testManagementAttemptToFixRetries
660
696
  attemptToFixTasks.add(task)
661
- taskToStatuses.set(task, [])
697
+ attemptToFixTaskToStatuses.set(task, [])
662
698
  }
663
699
  },
664
700
  })
@@ -726,15 +762,17 @@ function wrapVitestTestRunner (VitestTestRunner) {
726
762
 
727
763
  if (isTestManagementTestsEnabled) {
728
764
  const isAttemptingToFix = attemptToFixTasks.has(task)
729
- const isDisabled = disabledTasks.has(task)
730
765
  const isQuarantined = quarantinedTasks.has(task)
731
766
 
732
- if (isAttemptingToFix && (isDisabled || isQuarantined)) {
733
- if (task.result.state === 'fail') {
767
+ if (isAttemptingToFix) {
768
+ const statuses = attemptToFixTaskToStatuses.get(task)
769
+ if (task.result.state === 'pass' && statuses?.includes('fail')) {
734
770
  switchedStatuses.add(task)
771
+ task.result.state = 'fail'
735
772
  }
736
- task.result.state = 'pass'
737
- } else if (isQuarantined) {
773
+ }
774
+
775
+ if (!isAttemptingToFix && isQuarantined) {
738
776
  if (task.result.state === 'fail') {
739
777
  switchedStatuses.add(task)
740
778
  }
@@ -822,10 +860,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
822
860
 
823
861
  const lastExecutionStatus = task.result.state
824
862
  const isAtf = attemptToFixTasks.has(task)
825
- const isQuarantinedOrDisabledAtf = isAtf && (quarantinedTasks.has(task) || disabledTasks.has(task))
826
863
  const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
827
- const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isQuarantinedOrDisabledAtf
828
- const statuses = taskToStatuses.get(task)
864
+ const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
865
+ const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
829
866
 
830
867
  // These clauses handle task.repeats, whether EFD is enabled or not
831
868
  // The only thing that EFD does is to forcefully pass the test if it has passed at least once
@@ -842,7 +879,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
842
879
  } else {
843
880
  testPassCh.publish({ task, ...ctx.currentStore })
844
881
  }
845
- if (shouldTrackStatuses) {
882
+ if (shouldTrackStatuses && statuses) {
846
883
  statuses.push(lastExecutionStatus)
847
884
  }
848
885
  if (shouldFlipStatus) {
@@ -855,7 +892,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
855
892
  }
856
893
  }
857
894
  } else if (numRepetition === task.repeats) {
858
- if (shouldTrackStatuses) {
895
+ if (shouldTrackStatuses && statuses) {
859
896
  statuses.push(lastExecutionStatus)
860
897
  }
861
898
 
@@ -893,6 +930,14 @@ function wrapVitestTestRunner (VitestTestRunner) {
893
930
  }
894
931
  taskToCtx.set(task, ctx)
895
932
 
933
+ if (attemptToFixTasks.has(task)) {
934
+ logAttemptToFixTestExecution(
935
+ getTestSuitePath(task.file.filepath, process.cwd()),
936
+ testName,
937
+ loggedAttemptToFixTests
938
+ )
939
+ }
940
+
896
941
  testStartCh.runStores(ctx, () => {})
897
942
 
898
943
  // Wrap the test function so it runs inside the test span context.
@@ -960,11 +1005,11 @@ function wrapVitestTestRunner (VitestTestRunner) {
960
1005
  let attemptToFixPassed = false
961
1006
  let attemptToFixFailed = false
962
1007
  if (attemptToFixTasks.has(task)) {
963
- const statuses = taskToStatuses.get(task)
1008
+ const statuses = attemptToFixTaskToStatuses.get(task)
964
1009
  if (statuses.length === testManagementAttemptToFixRetries) {
965
- if (statuses.every(status => status === 'pass')) {
1010
+ if (status === 'pass' && statuses.every(status => status === 'pass')) {
966
1011
  attemptToFixPassed = true
967
- } else if (statuses.includes('fail')) {
1012
+ } else if (status === 'fail' || statuses.includes('fail')) {
968
1013
  attemptToFixFailed = true
969
1014
  }
970
1015
  }
@@ -1165,9 +1210,16 @@ addHook({
1165
1210
  // We have to trick vitest into thinking that the test has passed
1166
1211
  // but we want to report it as failed if it did fail
1167
1212
  const isSwitchedStatus = switchedStatuses.has(task)
1213
+ const providedContext = getProvidedContext()
1168
1214
 
1169
1215
  if (result) {
1170
1216
  const { state, duration, errors } = result
1217
+ const testError = errors?.[0]
1218
+ if (attemptToFixTasks.has(task)) {
1219
+ const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
1220
+ recordFinalAttemptToFixExecution(task, status, providedContext)
1221
+ }
1222
+
1171
1223
  if (state === 'skip') { // programmatic skip
1172
1224
  testSkipCh.publish({
1173
1225
  testName: getTestName(task),
@@ -1177,24 +1229,19 @@ addHook({
1177
1229
  })
1178
1230
  } else if (state === 'pass' && !isSwitchedStatus) {
1179
1231
  if (testCtx) {
1232
+ const isSkippedByTestManagement =
1233
+ !attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
1180
1234
  testPassCh.publish({
1181
1235
  task,
1182
- finalStatus:
1183
- disabledTasks.has(task) || quarantinedTasks.has(task) ? 'skip' : 'pass',
1236
+ finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
1184
1237
  ...testCtx.currentStore,
1185
1238
  })
1186
1239
  }
1187
1240
  } else if (state === 'fail' || isSwitchedStatus) {
1188
- let testError
1189
-
1190
- if (errors?.length) {
1191
- testError = errors[0]
1192
- }
1193
-
1194
1241
  let hasFailedAllRetries = false
1195
1242
  let attemptToFixFailed = false
1196
1243
  if (attemptToFixTasks.has(task)) {
1197
- const statuses = taskToStatuses.get(task)
1244
+ const statuses = attemptToFixTaskToStatuses.get(task)
1198
1245
  if (statuses.includes('fail')) {
1199
1246
  attemptToFixFailed = true
1200
1247
  }
@@ -1204,7 +1251,6 @@ addHook({
1204
1251
  }
1205
1252
 
1206
1253
  // Check if all EFD retries failed
1207
- const providedContext = getProvidedContext()
1208
1254
  const isEfdRetry =
1209
1255
  providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
1210
1256
  if (isEfdRetry) {
@@ -1232,7 +1278,7 @@ addHook({
1232
1278
 
1233
1279
  let finalStatus
1234
1280
  if (isSwitchedStatus) {
1235
- if (disabledTasks.has(task) || quarantinedTasks.has(task)) {
1281
+ if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
1236
1282
  finalStatus = 'skip'
1237
1283
  } else if (isAtrRetry || isEfdRetry) {
1238
1284
  finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
@@ -149,7 +149,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
149
149
  }
150
150
  this.addResponseTags(span, response)
151
151
 
152
- if (this._tracerConfig?.trace?.aws?.addSpanPointers) {
152
+ if (this._tracerConfig?.DD_TRACE_AWS_ADD_SPAN_POINTERS) {
153
153
  this.addSpanPointers(span, response)
154
154
  }
155
155
  })
@@ -118,7 +118,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
118
118
  return this.dynamoPrimaryKeyConfig
119
119
  }
120
120
 
121
- const configStr = this._tracerConfig?.trace?.dynamoDb?.tablePrimaryKeys
121
+ const configStr = this._tracerConfig?.DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS
122
122
  if (!configStr) {
123
123
  log.warn(
124
124
  // eslint-disable-next-line @stylistic/max-len
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../../dd-trace/src/log')
3
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
5
  const { getMessageSize } = require('../../dd-trace/src/datastreams')
5
6
 
@@ -68,13 +69,13 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
68
69
  const ddCarrier = metadata._datadog
69
70
  if (!ddCarrier) return
70
71
 
71
- // Clean up only our _datadog key, preserve other metadata
72
- delete metadata._datadog
72
+ // Avoid `delete`'s hidden-class transition; JSON.stringify also omits undefined values.
73
+ metadata._datadog = undefined
73
74
  job.opts.telemetry.metadata = JSON.stringify(metadata)
74
75
 
75
76
  return ddCarrier
76
- } catch {
77
- // Ignore malformed metadata
77
+ } catch (error) {
78
+ log.warn('bullmq: skipping _datadog extract on malformed telemetry.metadata: %s', error.message)
78
79
  }
79
80
  }
80
81
  }
@@ -1,8 +1,22 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../../dd-trace/src/log')
3
4
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
5
  const { DsmPathwayCodec, getMessageSize } = require('../../dd-trace/src/datastreams')
5
6
 
7
+ // Customer-controlled metadata may be malformed JSON. Returning a fresh `{}`
8
+ // on parse failure keeps the publish path alive instead of throwing into
9
+ // `Queue.add` / `Queue.addBulk`.
10
+ function parseTelemetryMetadata (raw) {
11
+ if (!raw) return {}
12
+ try {
13
+ return JSON.parse(raw)
14
+ } catch (error) {
15
+ log.warn('bullmq: ignoring malformed telemetry.metadata: %s', error.message)
16
+ return {}
17
+ }
18
+ }
19
+
6
20
  class BaseBullmqProducerPlugin extends ProducerPlugin {
7
21
  static id = 'bullmq'
8
22
 
@@ -42,12 +56,14 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
42
56
  throw new Error('injectTraceContext must be implemented by subclass')
43
57
  }
44
58
 
59
+ // Returned so setProducerCheckpoint can mutate it without a second parse.
45
60
  _injectIntoOpts (span, opts) {
46
61
  const carrier = {}
47
62
  this.tracer.inject(span, 'text_map', carrier)
48
- const existing = opts.telemetry?.metadata ? JSON.parse(opts.telemetry.metadata) : {}
49
- existing._datadog = carrier
50
- opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
63
+ const metadata = parseTelemetryMetadata(opts.telemetry?.metadata)
64
+ metadata._datadog = carrier
65
+ opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
66
+ return metadata
51
67
  }
52
68
 
53
69
  setProducerCheckpoint (span, ctx) {
@@ -56,10 +72,10 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
56
72
  const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
57
73
 
58
74
  if (optsTarget && typeof optsTarget === 'object') {
59
- const existing = optsTarget.telemetry?.metadata ? JSON.parse(optsTarget.telemetry.metadata) : {}
60
- DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
61
- if (!existing._datadog) existing._datadog = {}
62
- optsTarget.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
75
+ const metadata = ctx._ddMetadata ?? parseTelemetryMetadata(optsTarget.telemetry?.metadata)
76
+ DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
77
+ if (!metadata._datadog) metadata._datadog = {}
78
+ optsTarget.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
63
79
  }
64
80
  }
65
81
 
@@ -96,7 +112,7 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
96
112
 
97
113
  injectTraceContext (span, ctx) {
98
114
  const opts = this.#ensureOpts(ctx)
99
- this._injectIntoOpts(span, opts)
115
+ ctx._ddMetadata = this._injectIntoOpts(span, opts)
100
116
  }
101
117
 
102
118
  getDsmData (ctx) {
@@ -132,39 +148,31 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
132
148
  const jobs = ctx.arguments?.[0]
133
149
  if (!Array.isArray(jobs)) return
134
150
 
135
- for (const job of jobs) {
151
+ const cache = []
152
+ for (let i = 0; i < jobs.length; i++) {
153
+ const job = jobs[i]
136
154
  if (!job) continue
137
155
  job.opts = job.opts || {}
138
- this._injectIntoOpts(span, job.opts)
139
- }
140
- }
141
-
142
- getDsmData (ctx) {
143
- const jobs = ctx.arguments?.[0] || []
144
- const payloadSize = jobs.reduce((total, job) => {
145
- return total + (job?.data ? getMessageSize(job.data) : 0)
146
- }, 0)
147
- return {
148
- queueName: ctx.self?.name || 'bullmq',
149
- payloadSize,
150
- optsTarget: jobs[0]?.opts,
156
+ cache[i] = this._injectIntoOpts(span, job.opts)
151
157
  }
158
+ ctx._ddMetadata = cache
152
159
  }
153
160
 
154
161
  setProducerCheckpoint (span, ctx) {
155
162
  const jobs = ctx.arguments?.[0] || []
156
163
  const queueName = ctx.self?.name || 'bullmq'
157
164
  const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
165
+ const cache = ctx._ddMetadata
158
166
 
159
- for (const job of jobs) {
167
+ for (let i = 0; i < jobs.length; i++) {
168
+ const job = jobs[i]
160
169
  if (!job?.data) continue
161
170
  const payloadSize = getMessageSize(job.data)
162
171
  const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
163
- job.opts = job.opts || {}
164
- const existing = job.opts.telemetry?.metadata ? JSON.parse(job.opts.telemetry.metadata) : {}
165
- DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
166
- if (!existing._datadog) existing._datadog = {}
167
- job.opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
172
+ const metadata = cache?.[i] ?? parseTelemetryMetadata(job.opts.telemetry?.metadata)
173
+ DsmPathwayCodec.encode(dataStreamsContext, metadata._datadog || metadata)
174
+ if (!metadata._datadog) metadata._datadog = {}
175
+ job.opts.telemetry = { metadata: JSON.stringify(metadata), omitContext: true }
168
176
  }
169
177
  }
170
178
  }
@@ -187,7 +195,7 @@ class FlowProducerAddPlugin extends BaseBullmqProducerPlugin {
187
195
  const flow = ctx.arguments?.[0]
188
196
  if (!flow) return
189
197
  flow.opts = flow.opts || {}
190
- this._injectIntoOpts(span, flow.opts)
198
+ ctx._ddMetadata = this._injectIntoOpts(span, flow.opts)
191
199
  }
192
200
 
193
201
  getDsmData (ctx) {