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
@@ -4,6 +4,7 @@
4
4
  const { performance } = require('perf_hooks')
5
5
  const dateNow = Date.now
6
6
 
7
+ const satisfies = require('../../../vendor/dist/semifies')
7
8
  const {
8
9
  TEST_STATUS,
9
10
  TEST_IS_RUM_ACTIVE,
@@ -55,7 +56,9 @@ const {
55
56
  TEST_IS_MODIFIED,
56
57
  TEST_HAS_DYNAMIC_NAME,
57
58
  DYNAMIC_NAME_RE,
58
- logDynamicNamesWarning,
59
+ recordAttemptToFixExecution,
60
+ logAttemptToFixTestExecution,
61
+ logTestOptimizationSummary,
59
62
  getPullRequestBaseBranch,
60
63
  TEST_FINAL_STATUS,
61
64
  } = require('../../dd-trace/src/plugins/util/test')
@@ -106,6 +109,7 @@ const {
106
109
  } = require('./source-map-utils')
107
110
 
108
111
  const TEST_FRAMEWORK_NAME = 'cypress'
112
+ let hasWarnedDeprecatedCypressVersion = false
109
113
 
110
114
  const CYPRESS_STATUS_TO_TEST_STATUS = {
111
115
  passed: 'pass',
@@ -134,6 +138,20 @@ function getCypressVersion (details) {
134
138
  return ''
135
139
  }
136
140
 
141
+ function warnDeprecatedCypressVersion (version) {
142
+ if (DD_MAJOR >= 6 || hasWarnedDeprecatedCypressVersion || !version || !satisfies(version, '<12.0.0')) {
143
+ return
144
+ }
145
+
146
+ hasWarnedDeprecatedCypressVersion = true
147
+ // console.warn does not seem to work reliably in Cypress, so use console.log instead.
148
+ // eslint-disable-next-line no-console
149
+ console.log(
150
+ 'WARNING: dd-trace support for Cypress<12.0.0 is deprecated' +
151
+ ' and will not be supported in dd-trace v6. Please upgrade Cypress to >=12.0.0.'
152
+ )
153
+ }
154
+
137
155
  function getRootDir (details) {
138
156
  if (details?.config) {
139
157
  return details.config.projectRoot || details.config.repoRoot || process.cwd()
@@ -278,9 +296,8 @@ function getFinalStatus ({
278
296
  isQuarantined,
279
297
  isDisabled,
280
298
  }) {
281
- // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
282
- // the final status of its last execution should be reported as 'skip'.
283
- if (isQuarantined || isDisabled || status === 'skip') {
299
+ // If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
300
+ if (status === 'skip' || (retryKind !== FINAL_STATUS_RETRY_KIND.atf && (isQuarantined || isDisabled))) {
284
301
  return 'skip'
285
302
  }
286
303
 
@@ -322,6 +339,8 @@ class CypressPlugin {
322
339
  isImpactedTestsEnabled = false
323
340
  modifiedFiles = []
324
341
  newTestsWithDynamicNames = new Set()
342
+ attemptToFixExecutions = new Map()
343
+ loggedAttemptToFixTests = new Set()
325
344
 
326
345
  constructor () {
327
346
  const {
@@ -394,6 +413,8 @@ class CypressPlugin {
394
413
  this.testManagementTests = undefined
395
414
  this.isImpactedTestsEnabled = false
396
415
  this.modifiedFiles = []
416
+ this.attemptToFixExecutions = new Map()
417
+ this.loggedAttemptToFixTests = new Set()
397
418
  this.activeTestSpan = null
398
419
  this.testSuiteSpan = null
399
420
  this.testModuleSpan = null
@@ -429,6 +450,7 @@ class CypressPlugin {
429
450
  this._isInit = true
430
451
  this.tracer = tracer
431
452
  this.cypressConfig = cypressConfig
453
+ warnDeprecatedCypressVersion(cypressConfig.version)
432
454
 
433
455
  this.isTestIsolationEnabled = getIsTestIsolationEnabled(cypressConfig)
434
456
 
@@ -800,7 +822,10 @@ class CypressPlugin {
800
822
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
801
823
  }
802
824
 
803
- logDynamicNamesWarning(this.newTestsWithDynamicNames)
825
+ logTestOptimizationSummary({
826
+ attemptToFixExecutions: this.attemptToFixExecutions,
827
+ newTestsWithDynamicNames: this.newTestsWithDynamicNames,
828
+ })
804
829
 
805
830
  this.testModuleSpan.finish()
806
831
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
@@ -939,10 +964,10 @@ class CypressPlugin {
939
964
  if (cypressTest.displayError) {
940
965
  latestError = new Error(cypressTest.displayError)
941
966
  }
942
- // Update test status - but NOT for quarantined tests where we intentionally
967
+ // Update test status - but NOT for non-ATF quarantined tests where we intentionally
943
968
  // report 'fail' to Datadog even though Cypress sees it as 'pass'
944
969
  const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
945
- if (cypressTestStatus !== finishedTest.testStatus && !isQuarantinedTest) {
970
+ if (cypressTestStatus !== finishedTest.testStatus && (!isQuarantinedTest || finishedTest.isAttemptToFix)) {
946
971
  finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
947
972
  finishedTest.testSpan.setTag('error', latestError)
948
973
  }
@@ -1050,6 +1075,10 @@ class CypressPlugin {
1050
1075
  return { shouldSkip: true }
1051
1076
  }
1052
1077
 
1078
+ if (isAttemptToFix) {
1079
+ logAttemptToFixTestExecution(testSuite, testName, this.loggedAttemptToFixTests)
1080
+ }
1081
+
1053
1082
  // For disabled tests (not attemptToFix), skip them
1054
1083
  if (!isAttemptToFix && isDisabled) {
1055
1084
  return { shouldSkip: true }
@@ -1090,6 +1119,7 @@ class CypressPlugin {
1090
1119
  isAttemptToFix,
1091
1120
  isModified,
1092
1121
  isQuarantined: isQuarantinedFromSupport,
1122
+ isDisabled: isDisabledFromSupport,
1093
1123
  } = test
1094
1124
  if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
1095
1125
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -1119,6 +1149,7 @@ class CypressPlugin {
1119
1149
  this.testStatuses[testName] = [testStatus]
1120
1150
  }
1121
1151
  const testStatuses = this.testStatuses[testName]
1152
+ const activeSpanTags = this.activeTestSpan.context()._tags
1122
1153
 
1123
1154
  if (error) {
1124
1155
  this.activeTestSpan.setTag('error', error)
@@ -1194,6 +1225,13 @@ class CypressPlugin {
1194
1225
  this.activeTestSpan.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
1195
1226
  }
1196
1227
  }
1228
+ recordAttemptToFixExecution(this.attemptToFixExecutions, {
1229
+ testSuite,
1230
+ testName,
1231
+ status: testStatus,
1232
+ isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
1233
+ isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
1234
+ })
1197
1235
  }
1198
1236
  // ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
1199
1237
  if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
@@ -1207,6 +1245,9 @@ class CypressPlugin {
1207
1245
  if (isQuarantinedFromSupport) {
1208
1246
  this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
1209
1247
  }
1248
+ if (isDisabledFromSupport) {
1249
+ this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
1250
+ }
1210
1251
 
1211
1252
  const finishedTest = {
1212
1253
  testName,
@@ -1222,13 +1263,12 @@ class CypressPlugin {
1222
1263
  this.finishedTestsByFile[testSuite] = [finishedTest]
1223
1264
  }
1224
1265
  // test spans are finished at after:spec
1225
- const activeSpanTags = this.activeTestSpan.context()._tags
1226
1266
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
1227
1267
  hasCodeOwners: !!activeSpanTags[TEST_CODE_OWNERS],
1228
1268
  isNew,
1229
1269
  isRum: isRUMActive,
1230
1270
  browserDriver: 'cypress',
1231
- isQuarantined: isQuarantinedFromSupport,
1271
+ isQuarantined: activeSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
1232
1272
  isModified,
1233
1273
  isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
1234
1274
  })
@@ -23,22 +23,13 @@ const noopTask = {
23
23
  module.exports = function CypressPlugin (on, config) {
24
24
  const tracer = require('../../dd-trace')
25
25
 
26
- if (satisfies(config.version, '<10.2.0')) {
27
- if (DD_MAJOR >= 6) {
28
- // eslint-disable-next-line no-console
29
- console.error(
30
- 'ERROR: dd-trace v6 has deleted support for Cypress<10.2.0.'
31
- )
32
- on('task', noopTask)
33
- return config
34
- }
35
-
36
- // console.warn does not seem to work in cypress, so using console.log instead
26
+ if (DD_MAJOR >= 6 && satisfies(config.version, '<12.0.0')) {
37
27
  // eslint-disable-next-line no-console
38
- console.log(
39
- 'WARNING: dd-trace support for Cypress<10.2.0 is deprecated' +
40
- ' and will not be supported in future versions of dd-trace.'
28
+ console.error(
29
+ 'ERROR: dd-trace v6 has deleted support for Cypress<12.0.0.'
41
30
  )
31
+ on('task', noopTask)
32
+ return config
42
33
  }
43
34
 
44
35
  // The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
@@ -15,8 +15,8 @@ let isModifiedTest = false
15
15
  let isTestIsolationEnabled = false
16
16
  // Array of test names that have been retried and the reason
17
17
  const retryReasonsByTestName = new Map()
18
- // Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
19
- const quarantinedTestErrors = new Map()
18
+ // Track test errors suppressed by test management so we can still report them to Datadog.
19
+ const suppressedTestFailures = new Map()
20
20
 
21
21
  // Track the most recently loaded window in the AUT. Updated via the 'window:load'
22
22
  // event so we always get the real app window (after cy.visit()), not the
@@ -61,13 +61,12 @@ Cypress.on('fail', (err, runnable) => {
61
61
  }
62
62
 
63
63
  const testName = runnable.fullTitle()
64
- const { isQuarantined, isDisabled } = getTestProperties(testName)
64
+ const { isAttemptToFix, isQuarantined, isDisabled } = getTestProperties(testName)
65
65
 
66
66
  // Suppress failures for quarantined or disabled tests so they don't affect the exit code.
67
- // This applies regardless of attempt-to-fix status: per spec, quarantined/disabled test
68
- // results are always ignored.
69
- if (isQuarantined || isDisabled) {
70
- quarantinedTestErrors.set(testName, err)
67
+ // Attempt-to-fix ignores quarantine/disabled suppression and keeps the normal framework result.
68
+ if (!isAttemptToFix && (isQuarantined || isDisabled)) {
69
+ suppressedTestFailures.set(testName, { error: err, isQuarantined, isDisabled })
71
70
  return
72
71
  }
73
72
 
@@ -245,13 +244,14 @@ afterEach(function () {
245
244
  const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
246
245
  const testName = currentTest.fullTitle()
247
246
 
248
- // Check if this was a quarantined test that we suppressed the failure for
249
- const quarantinedError = quarantinedTestErrors.get(testName)
250
- const isQuarantinedTestThatFailed = !!quarantinedError
247
+ // Check if this was a test management test that we suppressed the failure for.
248
+ const suppressedTestFailure = suppressedTestFailures.get(testName)
249
+ const suppressedError = suppressedTestFailure && suppressedTestFailure.error
250
+ const isTestManagementTestThatFailed = !!suppressedError
251
251
 
252
- // For quarantined tests, convert Error to a serializable format for cy.task
253
- const errorToReport = isQuarantinedTestThatFailed
254
- ? { message: quarantinedError.message, stack: quarantinedError.stack }
252
+ // For suppressed test management tests, convert Error to a serializable format for cy.task.
253
+ const errorToReport = isTestManagementTestThatFailed
254
+ ? { message: suppressedError.message, stack: suppressedError.stack }
255
255
  : currentTest.err
256
256
 
257
257
  const testInfo = {
@@ -259,16 +259,17 @@ afterEach(function () {
259
259
  testItTitle: currentTest.title,
260
260
  testSuite: Cypress.mocha.getRootSuite().file,
261
261
  testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
262
- // For quarantined tests, report the actual state (failed) to Datadog, not what Cypress thinks (passed)
263
- state: isQuarantinedTestThatFailed ? 'failed' : currentTest.state,
264
- // For quarantined tests, include the actual error that was suppressed
262
+ // Report the actual failed state to Datadog, not the pass state Cypress sees after suppression.
263
+ state: isTestManagementTestThatFailed ? 'failed' : currentTest.state,
264
+ // Include the actual error that was suppressed.
265
265
  error: errorToReport,
266
266
  isNew: currentTest._ddIsNew,
267
267
  isEfdRetry: currentTest._ddIsEfdRetry,
268
268
  isAttemptToFix: currentTest._ddIsAttemptToFix,
269
269
  isModified: currentTest._ddIsModified,
270
- // Mark quarantined tests that failed so the plugin knows to tag them appropriately
271
- isQuarantined: isQuarantinedTestThatFailed,
270
+ // Mark suppressed tests so the plugin can tag them with the correct test management reason.
271
+ isQuarantined: isTestManagementTestThatFailed && suppressedTestFailure.isQuarantined,
272
+ isDisabled: isTestManagementTestThatFailed && suppressedTestFailure.isDisabled,
272
273
  }
273
274
  try {
274
275
  const invocationDetails = Cypress.mocha.getRunner().currentRunnable.invocationDetails
@@ -292,9 +293,9 @@ afterEach(function () {
292
293
  // ignore error and continue
293
294
  }
294
295
 
295
- // Clean up the quarantined error tracking
296
- if (isQuarantinedTestThatFailed) {
297
- quarantinedTestErrors.delete(testName)
296
+ // Clean up the suppressed error tracking.
297
+ if (isTestManagementTestThatFailed) {
298
+ suppressedTestFailures.delete(testName)
298
299
  }
299
300
 
300
301
  cy.task('dd:afterEach', { test: testInfo, coverage })
@@ -64,7 +64,7 @@ class GrpcClientPlugin extends ClientPlugin {
64
64
 
65
65
  error ({ span = this.activeSpan, error }) {
66
66
  this.addCode(span, error.code)
67
- if (error.code && !this._tracerConfig.grpc.client.error.statuses.includes(error.code)) {
67
+ if (error.code && !this._tracerConfig.DD_GRPC_CLIENT_ERROR_STATUSES.includes(error.code)) {
68
68
  return
69
69
  }
70
70
  this.addError(error, span)
@@ -70,7 +70,7 @@ class GrpcServerPlugin extends ServerPlugin {
70
70
  if (!span) return
71
71
 
72
72
  this.addCode(span, error.code)
73
- if (error.code && !this._tracerConfig.grpc.server.error.statuses.includes(error.code)) {
73
+ if (error.code && !this._tracerConfig.DD_GRPC_SERVER_ERROR_STATUSES.includes(error.code)) {
74
74
  return
75
75
  }
76
76
  this.addError(error)
@@ -56,15 +56,8 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
56
56
 
57
57
  commit (commitList) {
58
58
  if (!this.config.dsmEnabled) return
59
- const keys = [
60
- 'consumer_group',
61
- 'type',
62
- 'partition',
63
- 'offset',
64
- 'topic',
65
- ]
66
- for (const commit of commitList.map(this.transformCommit)) {
67
- if (keys.some(key => !commit.hasOwnProperty(key))) continue
59
+ for (const rawCommit of commitList) {
60
+ const commit = this.transformCommit(rawCommit)
68
61
  this.tracer.setOffset(commit)
69
62
  }
70
63
  }
@@ -64,14 +64,8 @@ class KafkajsProducerPlugin extends ProducerPlugin {
64
64
 
65
65
  if (!this.config.dsmEnabled) return
66
66
  if (!commitList || !Array.isArray(commitList)) return
67
- const keys = [
68
- 'type',
69
- 'partition',
70
- 'offset',
71
- 'topic',
72
- ]
73
- for (const commit of commitList.map(r => this.transformProduceResponse(r, clusterId))) {
74
- if (keys.some(key => !commit.hasOwnProperty(key))) continue
67
+ for (const rawCommit of commitList) {
68
+ const commit = this.transformProduceResponse(rawCommit, clusterId)
75
69
  this.tracer.setOffset(commit)
76
70
  }
77
71
  }
@@ -160,10 +160,9 @@ function limitDepth (input) {
160
160
  input, output, depth,
161
161
  } = queue.pop()
162
162
  const nextDepth = depth + 1
163
- for (const key in input) {
164
- if (typeof input[key] === 'function') continue
165
-
163
+ for (const key of Object.keys(input)) {
166
164
  let child = input[key]
165
+ if (typeof child === 'function') continue
167
166
 
168
167
  if (isBSON(child)) {
169
168
  child = typeof child.toJSON === 'function' ? child.toJSON() : '?'
@@ -39,6 +39,7 @@ const {
39
39
  TEST_SUITE,
40
40
  TEST_HAS_DYNAMIC_NAME,
41
41
  DYNAMIC_NAME_RE,
42
+ TEST_FINAL_STATUS,
42
43
  } = require('../../dd-trace/src/plugins/util/test')
43
44
  const { RESOURCE_NAME } = require('../../../ext/tags')
44
45
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -319,6 +320,7 @@ class PlaywrightPlugin extends CiPlugin {
319
320
  hasFailedAttemptToFixRetries,
320
321
  isAtrRetry,
321
322
  isModified,
323
+ finalStatus,
322
324
  onDone,
323
325
  }) => {
324
326
  if (!span) return
@@ -379,6 +381,9 @@ class PlaywrightPlugin extends CiPlugin {
379
381
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
380
382
  }
381
383
  }
384
+ if (finalStatus) {
385
+ span.setTag(TEST_FINAL_STATUS, finalStatus)
386
+ }
382
387
  for (const step of steps) {
383
388
  const stepStartTime = step.startTime.getTime()
384
389
  const stepSpan = this.tracer.startSpan('playwright.step', {
@@ -451,6 +456,7 @@ class PlaywrightPlugin extends CiPlugin {
451
456
  )
452
457
 
453
458
  span.setTag(TEST_STATUS, 'skip')
459
+ span.setTag(TEST_FINAL_STATUS, 'skip')
454
460
 
455
461
  if (isNew) {
456
462
  span.setTag(TEST_IS_NEW, 'true')
@@ -157,6 +157,13 @@ class RouterPlugin extends WebPlugin {
157
157
  }
158
158
 
159
159
  function isMoreSpecificThan (routeA, routeB) {
160
+ // Concrete paths beat catch-all wildcards (`/*splat`, `/api/*`) on the same
161
+ // request so that `/foo/bar` wins over `/foo/*splat` regardless of length.
162
+ if (routeA && routeB) {
163
+ const aWild = hasWildcard(routeA)
164
+ const bWild = hasWildcard(routeB)
165
+ if (aWild !== bWild) return !aWild
166
+ }
160
167
  if (!routeIsRegex(routeA) && routeIsRegex(routeB)) {
161
168
  return true
162
169
  }
@@ -167,4 +174,10 @@ function routeIsRegex (route) {
167
174
  return route.includes('(/')
168
175
  }
169
176
 
177
+ function hasWildcard (route) {
178
+ // RegExp routes are encoded as `(/.../)` and may legitimately contain `*`,
179
+ // so only treat plain string patterns as wildcards.
180
+ return !routeIsRegex(route) && route.includes('*')
181
+ }
182
+
170
183
  module.exports = RouterPlugin
@@ -30,9 +30,10 @@ if (!global._ddtrace) {
30
30
  configurable: true,
31
31
  writable: true,
32
32
  })
33
-
34
- global._ddtrace.default = global._ddtrace
35
- global._ddtrace.tracer = global._ddtrace
36
33
  }
37
34
 
38
35
  module.exports = global._ddtrace
36
+ // Static aliases so cjs-module-lexer surfaces them as ESM named exports
37
+ // (`import { tracer } from 'dd-trace'`).
38
+ module.exports.tracer = global._ddtrace
39
+ module.exports.default = global._ddtrace
@@ -70,7 +70,7 @@ class AIGuard extends NoopAIGuard {
70
70
  constructor (tracer, config) {
71
71
  super()
72
72
 
73
- if (!config.apiKey || !config.appKey) {
73
+ if (!config.apiKey || !config.DD_APP_KEY) {
74
74
  log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
75
75
  this.#initialized = false
76
76
  return
@@ -78,7 +78,7 @@ class AIGuard extends NoopAIGuard {
78
78
  this.#tracer = tracer
79
79
  this.#headers = {
80
80
  'DD-API-KEY': config.apiKey,
81
- 'DD-APPLICATION-KEY': config.appKey,
81
+ 'DD-APPLICATION-KEY': config.DD_APP_KEY,
82
82
  'DD-AI-GUARD-VERSION': tracerVersion,
83
83
  'DD-AI-GUARD-SOURCE': 'SDK',
84
84
  'DD-AI-GUARD-LANGUAGE': 'nodejs',
@@ -8,6 +8,7 @@ const web = require('../plugins/util/web')
8
8
  const { ipHeaderList } = require('../plugins/util/ip_extractor')
9
9
  const { keepTrace } = require('../priority_sampler')
10
10
  const { ASM } = require('../standalone/product')
11
+ const { isEmpty } = require('../util')
11
12
  const { getActiveRequest } = require('./store')
12
13
  const {
13
14
  incrementWafInitMetric,
@@ -170,7 +171,9 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
170
171
  // Basic collection
171
172
  if (!shouldCollectEventHeaders) return mandatoryCollectedHeaders
172
173
 
173
- const responseHeaders = Object.keys(storedResponseHeaders).length === 0
174
+ // Skip the spread when the stored side is empty -- common during the early
175
+ // request lifecycle when no upstream response headers have been captured.
176
+ const responseHeaders = isEmpty(storedResponseHeaders)
174
177
  ? res.getHeaders()
175
178
  : { ...storedResponseHeaders, ...res.getHeaders() }
176
179
 
@@ -66,8 +66,18 @@ function removeAllBaggageItems () {
66
66
  return EMPTY_STORE
67
67
  }
68
68
 
69
+ /**
70
+ * @param {BaggageStore} items Frozen in place; do not mutate after.
71
+ */
72
+ function setAllBaggageItems (items) {
73
+ Object.freeze(items)
74
+ baggageStorage.enterWith(items)
75
+ return items
76
+ }
77
+
69
78
  module.exports = {
70
79
  setBaggageItem,
80
+ setAllBaggageItems,
71
81
  getBaggageItem,
72
82
  getAllBaggageItems,
73
83
  removeBaggageItem,
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { getEnvironmentVariable } = require('../config/helper')
4
4
  const { isTrue } = require('../util')
5
+ const { DD_MAJOR } = require('../../../../version')
5
6
 
6
7
  /**
7
8
  * Returns the current Lage package name if the Lage package name override is enabled.
@@ -9,7 +10,7 @@ const { isTrue } = require('../util')
9
10
  * @returns {string|undefined}
10
11
  */
11
12
  function getLagePackageName () {
12
- if (!isTrue(getEnvironmentVariable('DD_ENABLE_LAGE_PACKAGE_NAME'))) {
13
+ if (DD_MAJOR < 6 && !isTrue(getEnvironmentVariable('DD_ENABLE_LAGE_PACKAGE_NAME'))) {
13
14
  return
14
15
  }
15
16
 
@@ -7,39 +7,13 @@ const zlib = require('zlib')
7
7
  const { storage } = require('../../../../datadog-core')
8
8
  const log = require('../../log')
9
9
  const { httpAgent, httpsAgent } = require('../../exporters/common/agents')
10
+ const {
11
+ RATE_LIMIT_MAX_WAIT_MS,
12
+ isRetriableNetworkError,
13
+ singleJitteredDelay,
14
+ } = require('../../exporters/common/retry')
10
15
  const { urlToHttpOptions } = require('../../exporters/common/url-to-http-options-polyfill')
11
16
 
12
- const RATE_LIMIT_MAX_WAIT_MS = 30_000
13
- const RETRY_BASE_MS = 5000
14
- const RETRY_JITTER_MS = 2500
15
-
16
- /**
17
- * Calculates retry delay with jitter to prevent thundering herd.
18
- * Delay is RETRY_BASE_MS + random(0, RETRY_JITTER_MS) (e.g. 5–7.5 seconds).
19
- *
20
- * @returns {number} Delay in milliseconds
21
- */
22
- function getRetryDelay () {
23
- return RETRY_BASE_MS + (Math.random() * RETRY_JITTER_MS)
24
- }
25
-
26
- /**
27
- * Determines if a network error is retriable (transient failures only).
28
- * ECONNREFUSED is retried because it can be transient (service starting up,
29
- * restarts, rolling deploys, k8s pod/readiness transitions). ENOTFOUND is
30
- * excluded as it indicates DNS failure or wrong host and is usually not transient.
31
- *
32
- * @param {Error} err - The error to check
33
- * @returns {boolean}
34
- */
35
- function isRetriableNetworkError (err) {
36
- if (!err.code) return false
37
- return err.code === 'ECONNREFUSED' ||
38
- err.code === 'ECONNRESET' ||
39
- err.code === 'ETIMEDOUT' ||
40
- err.code === 'EPIPE'
41
- }
42
-
43
17
  function parseUrl (urlObjOrString) {
44
18
  if (urlObjOrString !== null && typeof urlObjOrString === 'object') {
45
19
  return urlToHttpOptions(urlObjOrString)
@@ -63,6 +37,10 @@ function parseUrl (urlObjOrString) {
63
37
  * Destroys connections on errors to prevent reuse of bad connections. Preserves
64
38
  * original status code across retries for telemetry.
65
39
  *
40
+ * Retry timers stay ref'd. Test-runner plugins block the suite via
41
+ * `delay: true` channels until this callback fires; an unref'd retry would
42
+ * let the host exit first and the suite would never run.
43
+ *
66
44
  * @param {string} data - Request body (e.g. JSON string)
67
45
  * @param {object} options - { url, path?, method?, headers?, timeout? } (may be mutated)
68
46
  * @param {Function} callback - (err, res, statusCode) => void
@@ -157,7 +135,7 @@ function request (data, options, callback) {
157
135
  // ignore
158
136
  }
159
137
  hasRetried = true
160
- setTimeout(makeRequest, getRetryDelay())
138
+ setTimeout(makeRequest, singleJitteredDelay())
161
139
  return
162
140
  }
163
141
 
@@ -177,7 +155,7 @@ function request (data, options, callback) {
177
155
  // Retry on retriable network errors
178
156
  if (!hasRetried && isRetriableNetworkError(err)) {
179
157
  hasRetried = true
180
- setTimeout(makeRequest, getRetryDelay())
158
+ setTimeout(makeRequest, singleJitteredDelay())
181
159
  return
182
160
  }
183
161
 
@@ -8,7 +8,6 @@ export interface ConfigProperties extends GeneratedConfig {
8
8
  responsesEnabled: boolean
9
9
  rules: PayloadTaggingRules
10
10
  }
11
- commitSHA: string | undefined
12
11
  debug: boolean
13
12
  instrumentationSource: 'manual' | 'ssi'
14
13
  isCiVisibility: boolean
@@ -18,7 +17,6 @@ export interface ConfigProperties extends GeneratedConfig {
18
17
  lookup: NonNullable<import('../../../../index').TracerOptions['lookup']>
19
18
  readonly parsedDdTags: Record<string, string>
20
19
  plugins: boolean
21
- repositoryUrl: string | undefined
22
20
  sampler: {
23
21
  rateLimit: number
24
22
  rules: import('../../../../index').SamplingRule[]