dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -78,6 +78,7 @@ const {
78
78
  TEST_HAS_DYNAMIC_NAME,
79
79
  getIsFaultyEarlyFlakeDetection,
80
80
  DYNAMIC_NAME_RE,
81
+ isMarkedAsUnskippable,
81
82
  recordAttemptToFixExecution,
82
83
  recordTestManagementExecution,
83
84
  logAttemptToFixTestExecution,
@@ -86,7 +87,6 @@ const {
86
87
  TEST_FINAL_STATUS,
87
88
  getTestOptimizationRequestResults,
88
89
  } = require('../../dd-trace/src/plugins/util/test')
89
- const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
90
90
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
91
91
  const { RESOURCE_NAME } = require('../../../ext/tags')
92
92
  const getConfig = require('../../dd-trace/src/config')
@@ -477,7 +477,7 @@ class CypressPlugin {
477
477
  testsToSkip = []
478
478
  skippedTests = []
479
479
  skippedTestIds = new Set()
480
- skippableTestsCoverage = {}
480
+ skippableTestsCoverage
481
481
  testSessionCoverageMap = createCoverageMap()
482
482
  hasForcedToRunSuites = false
483
483
  hasUnskippableSuites = false
@@ -492,6 +492,7 @@ class CypressPlugin {
492
492
  loggedAttemptToFixTests = new Set()
493
493
  uploadedScreenshotPaths = new Set()
494
494
  screenshotUploadPromisesByTraceId = new Map()
495
+ screenshotUploadAbortControllers = new Set()
495
496
  afterScreenshotHandler = undefined
496
497
  lastFinishedTest = null
497
498
  pendingScreenshotUploads = []
@@ -565,7 +566,7 @@ class CypressPlugin {
565
566
  this.testsToSkip = []
566
567
  this.skippedTests = []
567
568
  this.skippedTestIds = new Set()
568
- this.skippableTestsCoverage = {}
569
+ this.skippableTestsCoverage = undefined
569
570
  this.testSessionCoverageMap = createCoverageMap()
570
571
  this.hasForcedToRunSuites = false
571
572
  this.hasUnskippableSuites = false
@@ -581,10 +582,12 @@ class CypressPlugin {
581
582
  this.loggedAttemptToFixTests = new Set()
582
583
  this.uploadedScreenshotPaths = new Set()
583
584
  this.screenshotUploadPromisesByTraceId = new Map()
585
+ this.screenshotUploadAbortControllers = new Set()
584
586
  this.lastFinishedTest = null
585
587
  this.pendingScreenshotUploads = []
586
588
  this.activeTestSpan = null
587
589
  this.testSuiteSpan = null
590
+ this.finishedTestSuiteSpans = []
588
591
  this.testModuleSpan = null
589
592
  this.testSessionSpan = null
590
593
  this.command = undefined
@@ -643,6 +646,19 @@ class CypressPlugin {
643
646
  return Promise.all(uploadPromises).then(getScreenshotUploadResult)
644
647
  }
645
648
 
649
+ /**
650
+ * Cancels screenshot work that must not outlive an errored after:spec finalization boundary.
651
+ *
652
+ * @param {Error} error - Error that triggered finalization
653
+ * @returns {void}
654
+ */
655
+ abortPendingScreenshotUploads (error) {
656
+ for (const controller of this.screenshotUploadAbortControllers) controller.abort(error)
657
+ this.screenshotUploadAbortControllers.clear()
658
+ this.screenshotUploadPromisesByTraceId.clear()
659
+ this.pendingScreenshotUploads = []
660
+ }
661
+
646
662
  /**
647
663
  * Returns the current time in the same coordinate system used by span
648
664
  * start/finish. Captured at session span creation so it shares the same
@@ -663,17 +679,6 @@ class CypressPlugin {
663
679
  return this.repositoryRoot || this.rootDir || process.cwd()
664
680
  }
665
681
 
666
- /**
667
- * Returns whether the backend supplied skipped-test coverage data.
668
- *
669
- * @returns {boolean}
670
- */
671
- hasSkippableTestsCoverage () {
672
- return !!(this.skippableTestsCoverage &&
673
- typeof this.skippableTestsCoverage === 'object' &&
674
- Object.keys(this.skippableTestsCoverage).length > 0)
675
- }
676
-
677
682
  /**
678
683
  * Returns whether skipped test coverage should be backfilled into the session coverage map.
679
684
  *
@@ -683,7 +688,7 @@ class CypressPlugin {
683
688
  return this.isItrEnabled &&
684
689
  this.isCoverageReportUploadEnabled &&
685
690
  this.isTestsSkipped &&
686
- this.hasSkippableTestsCoverage()
691
+ this.skippableTestsCoverage !== undefined
687
692
  }
688
693
 
689
694
  /**
@@ -800,6 +805,17 @@ class CypressPlugin {
800
805
  // Depending on the received configuration, the Cypress configuration can be modified:
801
806
  // for example, to enable retries for failed tests.
802
807
  init (tracer, cypressConfig) {
808
+ if (this.cypressConfig === cypressConfig && this.hasOriginalCypressRetries) {
809
+ cypressConfig.retries = this.originalCypressRetries !== null &&
810
+ typeof this.originalCypressRetries === 'object'
811
+ ? { ...this.originalCypressRetries }
812
+ : this.originalCypressRetries
813
+ } else {
814
+ this.originalCypressRetries = cypressConfig.retries !== null && typeof cypressConfig.retries === 'object'
815
+ ? { ...cypressConfig.retries }
816
+ : cypressConfig.retries
817
+ this.hasOriginalCypressRetries = true
818
+ }
803
819
  this.resetRunState()
804
820
  this._isInit = true
805
821
  this.tracer = tracer
@@ -1063,9 +1079,13 @@ class CypressPlugin {
1063
1079
  }
1064
1080
 
1065
1081
  async beforeRun (details) {
1066
- // We need to make sure that the plugin is initialized before running the tests
1067
- // This is for the case where the user has not returned the promise from the init function
1068
- await this.libraryConfigurationPromise
1082
+ if (this._isInit) {
1083
+ // The user may not have returned the promise from the init function.
1084
+ await this.libraryConfigurationPromise
1085
+ } else {
1086
+ // Cypress open reuses the same plugin process for every interactive run.
1087
+ await this.init(this.tracer, this.cypressConfig)
1088
+ }
1069
1089
 
1070
1090
  this.command = getCypressCommand(details)
1071
1091
  this.frameworkVersion = getCypressVersion(details)
@@ -1131,7 +1151,7 @@ class CypressPlugin {
1131
1151
  } else {
1132
1152
  const { skippableTests, correlationId, skippableTestsCoverage } = skippableTestsResponse
1133
1153
  this.testsToSkip = skippableTests || []
1134
- this.skippableTestsCoverage = skippableTestsCoverage || {}
1154
+ this.skippableTestsCoverage = skippableTestsCoverage
1135
1155
  this.itrCorrelationId = correlationId
1136
1156
  incrementCountMetric(TELEMETRY_ITR_SKIPPED, { testLevel: 'test' }, this.testsToSkip.length)
1137
1157
  }
@@ -1252,18 +1272,29 @@ class CypressPlugin {
1252
1272
  return details
1253
1273
  }
1254
1274
 
1255
- afterRun (suiteStats) {
1275
+ afterRun (suiteStats, error, shouldFailFinishedSuites = true) {
1256
1276
  if (!this._isInit) {
1257
1277
  log.warn('Attemping to call afterRun without initializating the plugin first')
1258
1278
  return
1259
1279
  }
1260
1280
  if (this.testSessionSpan && this.testModuleSpan) {
1261
- const testStatus = getSessionStatus(suiteStats)
1281
+ const testStatus = error ? 'fail' : getSessionStatus(suiteStats)
1262
1282
  const hasBackfilledCoverage = this.applySkippedCoverageToTestSessionCoverage()
1263
1283
  const testCodeCoverageLinesTotal = this.getTestCodeCoverageLinesTotal(hasBackfilledCoverage)
1264
1284
 
1265
1285
  this.testModuleSpan.setTag(TEST_STATUS, testStatus)
1266
1286
  this.testSessionSpan.setTag(TEST_STATUS, testStatus)
1287
+ for (const span of this.finishedTestSuiteSpans) {
1288
+ if (error && shouldFailFinishedSuites) {
1289
+ span.setTag(TEST_STATUS, 'fail')
1290
+ span.setTag('error', error)
1291
+ }
1292
+ }
1293
+ this.finishedTestSuiteSpans = []
1294
+ if (error) {
1295
+ this.testModuleSpan.setTag('error', error)
1296
+ this.testSessionSpan.setTag('error', error)
1297
+ }
1267
1298
 
1268
1299
  addIntelligentTestRunnerSpanTags(
1269
1300
  this.testSessionSpan,
@@ -1301,6 +1332,7 @@ class CypressPlugin {
1301
1332
  })
1302
1333
 
1303
1334
  finishAllTraceSpans(this.testSessionSpan)
1335
+ this.tracer._tracer._exporter?.exportDeferredTestSuiteSpans?.()
1304
1336
  }
1305
1337
 
1306
1338
  return new Promise(resolve => {
@@ -1358,7 +1390,7 @@ class CypressPlugin {
1358
1390
  }
1359
1391
  }
1360
1392
 
1361
- afterSpec (spec, results) {
1393
+ afterSpec (spec, results, error) {
1362
1394
  const { tests, stats, screenshots } = results || {}
1363
1395
  const cypressTests = tests || []
1364
1396
  const specScreenshots = screenshots || []
@@ -1572,13 +1604,16 @@ class CypressPlugin {
1572
1604
  const screenshotUploadResultPromise = failedTestTraceId
1573
1605
  ? this.getScreenshotUploadResultPromise(failedTestTraceId)
1574
1606
  : undefined
1575
- if (screenshotUploadResultPromise) {
1607
+ if (screenshotUploadResultPromise && !error) {
1576
1608
  testSpanFinishPromises.push(screenshotUploadResultPromise.then((uploadResult) => {
1577
1609
  setScreenshotUploadTags(finishedTest.testSpan, uploadResult)
1578
1610
  this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
1579
1611
  finishedTest.testSpan.finish(finishedTest.finishTime)
1580
1612
  }))
1581
1613
  } else {
1614
+ if (screenshotUploadResultPromise) {
1615
+ this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
1616
+ }
1582
1617
  finishedTest.testSpan.finish(finishedTest.finishTime)
1583
1618
  }
1584
1619
  }
@@ -1586,13 +1621,18 @@ class CypressPlugin {
1586
1621
 
1587
1622
  const finishSuite = () => {
1588
1623
  if (this.testSuiteSpan) {
1589
- const status = getSuiteStatus(stats)
1624
+ const status = error ? 'fail' : getSuiteStatus(stats)
1590
1625
  this.testSuiteSpan.setTag(TEST_STATUS, status)
1591
1626
 
1592
- if (latestError) {
1593
- this.testSuiteSpan.setTag('error', latestError)
1627
+ if (error || latestError) {
1628
+ this.testSuiteSpan.setTag('error', error || latestError)
1594
1629
  }
1630
+ const canRunAfterRun = this.cypressConfig.isTextTerminal ||
1631
+ this.cypressConfig.experimentalInteractiveRunEvents
1632
+ const exporter = this.tracer._tracer._exporter
1633
+ if (canRunAfterRun && exporter?.deferTestSuiteSpan) exporter.deferTestSuiteSpan(this.testSuiteSpan)
1595
1634
  this.testSuiteSpan.finish()
1635
+ if (canRunAfterRun) this.finishedTestSuiteSpans.push(this.testSuiteSpan)
1596
1636
  this.testSuiteSpan = null
1597
1637
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
1598
1638
  }
@@ -1608,16 +1648,23 @@ class CypressPlugin {
1608
1648
 
1609
1649
  finishSuite()
1610
1650
 
1651
+ if (error) {
1652
+ this.abortPendingScreenshotUploads(error)
1653
+ return this.afterRun(undefined, error, false)
1654
+ }
1655
+
1611
1656
  const screenshotUploadsPromise = waitForScreenshotUploads()
1657
+ let afterSpecPromise = screenshotUploadsPromise
1612
1658
  if (testSpanFinishPromises.length > 0) {
1613
1659
  const testSpansPromise = Promise.all(testSpanFinishPromises).then(() => null)
1614
1660
  if (screenshotUploadsPromise) {
1615
- return Promise.all([testSpansPromise, screenshotUploadsPromise]).then(() => null)
1661
+ afterSpecPromise = Promise.all([testSpansPromise, screenshotUploadsPromise]).then(() => null)
1662
+ } else {
1663
+ afterSpecPromise = testSpansPromise
1616
1664
  }
1617
- return testSpansPromise
1618
1665
  }
1619
1666
 
1620
- return screenshotUploadsPromise
1667
+ return afterSpecPromise
1621
1668
  }
1622
1669
 
1623
1670
  /**
@@ -1637,6 +1684,7 @@ class CypressPlugin {
1637
1684
  }
1638
1685
 
1639
1686
  const uploadPromises = []
1687
+ const abortController = new AbortController()
1640
1688
 
1641
1689
  for (const screenshot of screenshots) {
1642
1690
  const filePath = getScreenshotFilePath(screenshot)
@@ -1655,6 +1703,7 @@ class CypressPlugin {
1655
1703
  traceId,
1656
1704
  idempotencyKey,
1657
1705
  capturedAtMs,
1706
+ signal: abortController.signal,
1658
1707
  }, (err) => {
1659
1708
  resolve(err ? SCREENSHOT_UPLOAD_RESULT_ERROR : SCREENSHOT_UPLOAD_RESULT_UPLOADED)
1660
1709
  })
@@ -1662,7 +1711,10 @@ class CypressPlugin {
1662
1711
  }
1663
1712
 
1664
1713
  if (uploadPromises.length > 0) {
1665
- const uploadPromise = Promise.all(uploadPromises).then(getScreenshotUploadResult)
1714
+ this.screenshotUploadAbortControllers.add(abortController)
1715
+ const uploadPromise = Promise.all(uploadPromises).then(getScreenshotUploadResult).finally(() => {
1716
+ this.screenshotUploadAbortControllers.delete(abortController)
1717
+ })
1666
1718
  this.addScreenshotUploadPromise(traceId, uploadPromise)
1667
1719
  return uploadPromise
1668
1720
  }
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const { AsyncLocalStorage } = require('node:async_hooks')
4
+
5
+ const log = require('../../dd-trace/src/log')
6
+
7
+ const DD_CYPRESS_USER_HANDLER_CONTEXT = Symbol.for('dd-trace.cypress.user-handler.context')
8
+ const userHandlerContext = globalThis[DD_CYPRESS_USER_HANDLER_CONTEXT] ||= new AsyncLocalStorage()
9
+ const manualPluginOwner = {}
10
+
11
+ /**
12
+ * Runs a Cypress user handler while allowing wrapped legacy Datadog helpers to
13
+ * defer to the managed finalizer.
14
+ *
15
+ * @param {() => unknown} handler user handler invocation
16
+ * @returns {unknown} handler result
17
+ */
18
+ function runUserHandler (handler) {
19
+ return userHandlerContext.run(true, handler)
20
+ }
21
+
22
+ /**
23
+ * @returns {boolean} whether a managed finalizer owns the current lifecycle event
24
+ */
25
+ function shouldDeferLegacyFinalization () {
26
+ return userHandlerContext.getStore() === true
27
+ }
28
+
29
+ /**
30
+ * Converts a Cypress rejection reason into error metadata without changing the
31
+ * value rethrown to Cypress.
32
+ *
33
+ * @param {unknown} reason rejection reason
34
+ * @returns {Error} error used for Test Optimization finalization
35
+ */
36
+ function normalizeUserHandlerError (reason) {
37
+ if (reason instanceof Error) return reason
38
+ if (reason == null) return new Error('Cypress user handler rejected without an error')
39
+
40
+ try {
41
+ return new Error(String(reason))
42
+ } catch {
43
+ return new Error('Cypress user handler failed')
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Runs the Datadog finalizer after user handlers while preserving the user
49
+ * error as the framework-visible failure.
50
+ *
51
+ * @param {Promise<void>} userHandlers collected user-handler chain
52
+ * @param {(userError?: unknown) => unknown} finalizer Datadog finalizer
53
+ * @returns {Promise<unknown>} finalizer result
54
+ */
55
+ function finalizeAfterUserHandlers (userHandlers, finalizer) {
56
+ return userHandlers.then(
57
+ () => finalizer(),
58
+ userError => Promise.resolve().then(() => {
59
+ return finalizer(normalizeUserHandlerError(userError))
60
+ }).then(
61
+ () => { throw userError },
62
+ finalizerError => {
63
+ log.error('Datadog Cypress finalizer failed after a user handler error', finalizerError)
64
+ throw userError
65
+ }
66
+ )
67
+ )
68
+ }
69
+
70
+ module.exports = {
71
+ finalizeAfterUserHandlers,
72
+ manualPluginOwner,
73
+ normalizeUserHandlerError,
74
+ runUserHandler,
75
+ shouldDeferLegacyFinalization,
76
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const NoopTracer = require('../../dd-trace/src/noop/tracer')
4
4
  const Plugin = require('../../dd-trace/src/plugins/plugin')
5
+ const { finalizeAfterUserHandlers, runUserHandler } = require('./finalization')
5
6
 
6
7
  /**
7
8
  * Cypress plugin handles setup-node-events from the instrumentation layer
@@ -30,11 +31,12 @@ class CypressPlugin extends Plugin {
30
31
  const registerAfterRunWithCleanup = (afterRunHandler) => {
31
32
  on('after:run', (results) => {
32
33
  const chain = userAfterRunHandlers.reduce(
33
- (p, h) => p.then(() => h(results)),
34
+ (p, h) => p.then(() => runUserHandler(() => h(results))),
34
35
  Promise.resolve()
35
36
  )
36
37
  if (afterRunHandler) {
37
- return chain.then(() => afterRunHandler(results)).finally(cleanupWrapper)
38
+ return finalizeAfterUserHandlers(chain, userError => afterRunHandler(results, userError))
39
+ .finally(cleanupWrapper)
38
40
  }
39
41
  return chain.finally(cleanupWrapper)
40
42
  })
@@ -82,7 +84,7 @@ class CypressPlugin extends Plugin {
82
84
  // (the chained registration replaces the one plugin.js set, so it must include it).
83
85
  if (userAfterSpecHandlers.length > 0) {
84
86
  on('after:spec', (spec, results) => userAfterSpecHandlers.reduce(
85
- (chain, handler) => chain.then(() => handler(spec, results)),
87
+ (chain, handler) => chain.then(() => runUserHandler(() => handler(spec, results))),
86
88
  Promise.resolve()
87
89
  ))
88
90
  }
@@ -97,13 +99,17 @@ class CypressPlugin extends Plugin {
97
99
 
98
100
  on('after:spec', (spec, results) => {
99
101
  const chain = userAfterSpecHandlers.reduce(
100
- (p, h) => p.then(() => h(spec, results)),
102
+ (p, h) => p.then(() => runUserHandler(() => h(spec, results))),
101
103
  Promise.resolve()
102
104
  )
103
- return chain.then(() => cypressPlugin.afterSpec(spec, results))
105
+ return finalizeAfterUserHandlers(chain, userError => cypressPlugin.afterSpec(spec, results, userError))
106
+ .catch((error) => {
107
+ cleanupWrapper()
108
+ throw error
109
+ })
104
110
  })
105
111
 
106
- registerAfterRunWithCleanup((results) => cypressPlugin.afterRun(results))
112
+ registerAfterRunWithCleanup((results, userError) => cypressPlugin.afterRun(results, userError))
107
113
 
108
114
  on('task', cypressPlugin.getTasks())
109
115
 
@@ -4,6 +4,12 @@ const NoopTracer = require('../../dd-trace/src/noop/tracer')
4
4
  const satisfies = require('../../../vendor/dist/semifies')
5
5
  const { DD_MAJOR } = require('../../../version')
6
6
  const cypressPlugin = require('./cypress-plugin')
7
+ const { manualPluginOwner } = require('./finalization')
8
+
9
+ const DD_CYPRESS_AFTER_SPEC_HANDLER = Symbol.for('dd-trace.cypress.after-spec.handler')
10
+ const DD_CYPRESS_AFTER_RUN_HANDLER = Symbol.for('dd-trace.cypress.after-run.handler')
11
+ const DD_CYPRESS_TASK_HANDLER = Symbol.for('dd-trace.cypress.task.handler')
12
+ const DD_CYPRESS_NOOP_TASK_HANDLER = Symbol.for('dd-trace.cypress.noop-task.handler')
7
13
 
8
14
  const noopTask = {
9
15
  'dd:testSuiteStart': () => {
@@ -18,6 +24,7 @@ const noopTask = {
18
24
  'dd:addTags': () => {
19
25
  return null
20
26
  },
27
+ [DD_CYPRESS_NOOP_TASK_HANDLER]: true,
21
28
  }
22
29
 
23
30
  module.exports = function CypressPlugin (on, config) {
@@ -41,9 +48,15 @@ module.exports = function CypressPlugin (on, config) {
41
48
 
42
49
  on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
43
50
  on('after:screenshot', cypressPlugin.getAfterScreenshotHandler())
44
- on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin))
45
- on('after:run', cypressPlugin.afterRun.bind(cypressPlugin))
46
- on('task', cypressPlugin.getTasks())
51
+ const afterSpecHandler = cypressPlugin.afterSpec.bind(cypressPlugin)
52
+ afterSpecHandler[DD_CYPRESS_AFTER_SPEC_HANDLER] = true
53
+ on('after:spec', afterSpecHandler)
54
+ const afterRunHandler = cypressPlugin.afterRun.bind(cypressPlugin)
55
+ afterRunHandler[DD_CYPRESS_AFTER_RUN_HANDLER] = true
56
+ on('after:run', afterRunHandler)
57
+ const taskHandler = cypressPlugin.getTasks()
58
+ taskHandler[DD_CYPRESS_TASK_HANDLER] = manualPluginOwner
59
+ on('task', taskHandler)
47
60
 
48
61
  return cypressPlugin.init(tracer, config)
49
62
  }
@@ -78,6 +78,10 @@ module.exports = class DdTraceApiPlugin extends Plugin {
78
78
  }
79
79
 
80
80
  // handleEvent('configure')
81
+ // No handoff for `openfeature` yet: an application that calls into the tracer
82
+ // exclusively through the `dd-trace-api` shim (instead of `require('dd-trace')`) cannot
83
+ // reach `tracer.openfeature` through that shim. This does not affect SSI itself --
84
+ // auto-injection and vendored-provider resolution work regardless of this gap.
81
85
  handleEvent('startSpan')
82
86
  handleEvent('wrap')
83
87
  handleEvent('trace')
@@ -1,11 +1,13 @@
1
1
  'use strict'
2
2
 
3
+ const { DD_MAJOR } = require('../../../version')
3
4
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
5
  const ElectronIpcPlugin = require('./ipc')
5
6
  const ElectronNetPlugin = require('./net')
6
7
 
7
8
  class ElectronPlugin extends CompositePlugin {
8
9
  static id = 'electron'
10
+ static optIn = DD_MAJOR >= 7
9
11
  static get plugins () {
10
12
  return {
11
13
  net: ElectronNetPlugin,
@@ -5,7 +5,7 @@ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
5
  class FsPlugin extends TracingPlugin {
6
6
  static id = 'fs'
7
7
  static operation = 'operation'
8
- static experimental = true
8
+ static optIn = true
9
9
 
10
10
  bindStart (ctx) {
11
11
  if (!this.activeSpan) return { noop: true }
@@ -1,8 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
3
+ const {
4
+ readDatadogParentId,
5
+ readDatadogSamplingPriority,
6
+ readDatadogTraceId,
7
+ readTraceparent,
8
+ writeDatadogParentId,
9
+ writeDatadogSamplingPriority,
10
+ writeDatadogTraceId,
11
+ } = require('../../dd-trace/src/carrier')
4
12
  const { DsmPathwayCodec, getHeadersSize } = require('../../dd-trace/src/datastreams')
5
13
  const id = require('../../dd-trace/src/id')
14
+ const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
6
15
 
7
16
  class GoogleCloudPubsubProducerPlugin extends ProducerPlugin {
8
17
  static id = 'google-cloud-pubsub'
@@ -21,7 +30,7 @@ class GoogleCloudPubsubProducerPlugin extends ProducerPlugin {
21
30
 
22
31
  handleMessagePublish ({ attributes, pubsub, topicName }) {
23
32
  // Skip if message already has trace context from upstream
24
- if (attributes['x-datadog-trace-id'] || attributes.traceparent) return
33
+ if (readDatadogTraceId(attributes) || readTraceparent(attributes)) return
25
34
 
26
35
  const activeSpan = this.tracer.scope().active()
27
36
  if (!activeSpan) return
@@ -62,7 +71,7 @@ class GoogleCloudPubsubProducerPlugin extends ProducerPlugin {
62
71
  const messages = request.messages || []
63
72
  const topic = request.topic
64
73
  const messageCount = messages.length
65
- const hasTraceContext = messages[0]?.attributes?.['x-datadog-trace-id']
74
+ const hasTraceContext = messages[0]?.attributes && readDatadogTraceId(messages[0].attributes)
66
75
 
67
76
  /**
68
77
  * Batch Publishing Strategy:
@@ -82,12 +91,14 @@ class GoogleCloudPubsubProducerPlugin extends ProducerPlugin {
82
91
  }
83
92
 
84
93
  const firstAttrs = messages[0]?.attributes
85
- const parentData = firstAttrs?.['x-datadog-trace-id'] && firstAttrs['x-datadog-parent-id']
94
+ const traceId = firstAttrs && readDatadogTraceId(firstAttrs)
95
+ const spanId = firstAttrs && readDatadogParentId(firstAttrs)
96
+ const parentData = traceId && spanId
86
97
  ? {
87
- traceId: firstAttrs['x-datadog-trace-id'],
88
- spanId: firstAttrs['x-datadog-parent-id'],
98
+ traceId,
99
+ spanId,
89
100
  traceIdUpper: firstAttrs['_dd.p.tid'],
90
- samplingPriority: firstAttrs['x-datadog-sampling-priority'],
101
+ samplingPriority: readDatadogSamplingPriority(firstAttrs),
91
102
  }
92
103
  : null
93
104
 
@@ -174,33 +185,36 @@ class GoogleCloudPubsubProducerPlugin extends ProducerPlugin {
174
185
  }
175
186
 
176
187
  #extractSpanLink (attrs) {
177
- if (!attrs?.['x-datadog-trace-id'] || !attrs['x-datadog-parent-id']) return null
188
+ if (!attrs) return null
189
+ const traceId = readDatadogTraceId(attrs)
190
+ const spanId = readDatadogParentId(attrs)
191
+ if (!traceId || !spanId) return null
178
192
 
179
193
  // Convert to hex strings
180
- const lowerHex = id(attrs['x-datadog-trace-id']).toString(16)
181
- const spanIdHex = id(attrs['x-datadog-parent-id']).toString(16)
194
+ const lowerHex = id(traceId).toString(16)
195
+ const spanIdHex = id(spanId).toString(16)
182
196
 
183
197
  // Build full 128-bit trace ID
184
198
  const traceIdHex = attrs['_dd.p.tid']
185
199
  ? attrs['_dd.p.tid'] + lowerHex
186
200
  : lowerHex.padStart(32, '0')
187
201
 
202
+ const samplingPriority = readDatadogSamplingPriority(attrs)
188
203
  return {
189
204
  traceId: traceIdHex,
190
205
  spanId: spanIdHex,
191
- samplingPriority: attrs['x-datadog-sampling-priority']
192
- ? Number.parseInt(attrs['x-datadog-sampling-priority'], 10)
206
+ samplingPriority: samplingPriority
207
+ ? Number.parseInt(samplingPriority, 10)
193
208
  : undefined,
194
209
  }
195
210
  }
196
211
 
197
212
  #extractParentContext (data) {
198
- const carrier = {
199
- 'x-datadog-trace-id': data.traceId,
200
- 'x-datadog-parent-id': data.spanId,
201
- }
213
+ const carrier = /** @type {Record<string, unknown>} */ ({})
214
+ writeDatadogTraceId(carrier, data.traceId)
215
+ writeDatadogParentId(carrier, data.spanId)
202
216
  if (data.traceIdUpper) carrier['_dd.p.tid'] = data.traceIdUpper
203
- if (data.samplingPriority) carrier['x-datadog-sampling-priority'] = String(data.samplingPriority)
217
+ if (data.samplingPriority) writeDatadogSamplingPriority(carrier, String(data.samplingPriority))
204
218
 
205
219
  return this.tracer.extract('text_map', carrier)
206
220
  }
@@ -5,6 +5,7 @@ const realSetTimeout = setTimeout
5
5
 
6
6
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
7
7
  const { storage } = require('../../datadog-core')
8
+ const { writeDatadogParentId, writeDatadogTraceId } = require('../../dd-trace/src/carrier')
8
9
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
9
10
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
10
11
 
@@ -112,6 +113,7 @@ class JestPlugin extends CiPlugin {
112
113
  hasUnskippableSuites,
113
114
  hasForcedToRunSuites,
114
115
  error,
116
+ isTestSessionFinalizationError,
115
117
  isEarlyFlakeDetectionEnabled,
116
118
  isEarlyFlakeDetectionFaulty,
117
119
  isTestManagementTestsEnabled,
@@ -122,6 +124,9 @@ class JestPlugin extends CiPlugin {
122
124
  this.testModuleSpan.setTag(TEST_STATUS, status)
123
125
 
124
126
  if (error) {
127
+ if (isTestSessionFinalizationError) {
128
+ this.tracer._exporter.setDeferredTestSuiteError?.(error)
129
+ }
125
130
  this.testSessionSpan.setTag('error', error)
126
131
  this.testModuleSpan.setTag('error', error)
127
132
  }
@@ -158,6 +163,7 @@ class JestPlugin extends CiPlugin {
158
163
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
159
164
  }
160
165
 
166
+ this.tracer._exporter.exportDeferredTestSuiteSpans?.()
161
167
  this.testModuleSpan.finish()
162
168
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
163
169
  this.testSessionSpan.finish()
@@ -230,10 +236,10 @@ class JestPlugin extends CiPlugin {
230
236
  _ddItrSkippingEnabledTags: itrSkippingEnabledTags,
231
237
  } = testEnvironmentOptions
232
238
 
233
- const testSessionSpanContext = this.tracer.extract('text_map', {
234
- 'x-datadog-trace-id': testSessionId,
235
- 'x-datadog-parent-id': testModuleId,
236
- })
239
+ const carrier = /** @type {Record<string, unknown>} */ ({})
240
+ writeDatadogTraceId(carrier, testSessionId)
241
+ writeDatadogParentId(carrier, testModuleId)
242
+ const testSessionSpanContext = this.tracer.extract('text_map', carrier)
237
243
 
238
244
  const testSuiteMetadata = {
239
245
  ...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest'),
@@ -328,6 +334,7 @@ class JestPlugin extends CiPlugin {
328
334
  this.pendingTestSuiteFinishes.add(pendingFinish)
329
335
 
330
336
  const finish = () => {
337
+ this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
331
338
  testSuiteSpan.finish()
332
339
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
333
340
  // Suites potentially run in a different process than the session,
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
- const { getMessageSize } = require('../../dd-trace/src/datastreams')
5
- const { convertToTextMap } = require('./utils')
4
+ const { convertToTextMap, getKafkaMessageSize } = require('./utils')
6
5
 
7
6
  class KafkajsBatchConsumerPlugin extends ConsumerPlugin {
8
7
  static id = 'kafkajs'
@@ -39,7 +38,7 @@ class KafkajsBatchConsumerPlugin extends ConsumerPlugin {
39
38
  }
40
39
 
41
40
  if (!this.config.dsmEnabled) continue
42
- const payloadSize = getMessageSize(message)
41
+ const payloadSize = getKafkaMessageSize(message)
43
42
  this.tracer.decodeDataStreamsContext(headers)
44
43
  const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
45
44
  if (clusterId) {