dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -84,6 +84,7 @@ const numAttemptToCtx = new Map()
84
84
  const newTestsByTestFullname = new Map()
85
85
  const attemptToFixTestsByTestFullname = new Map()
86
86
  const modifiedTestsByPickleId = new Map()
87
+ const runnerToRetryState = new WeakMap()
87
88
  // Pickle IDs for tests that are genuinely new (not in known tests list).
88
89
  const newTestPickleIds = new Set()
89
90
  const attemptToFixExecutions = new Map()
@@ -662,18 +663,97 @@ function getFinalStatus ({
662
663
  }
663
664
  }
664
665
 
666
+ async function handleRetriedAttempt (runner, state) {
667
+ const { promises } = state
668
+ if (promises.hitBreakpointPromise) {
669
+ await promises.hitBreakpointPromise
670
+ }
671
+
672
+ const setProbePromise = publishRetriedAttempt(runner, state)
673
+ if (setProbePromise) {
674
+ await setProbePromise
675
+ promises.setProbePromise = undefined
676
+ }
677
+
678
+ startRetriedAttempt(state)
679
+ }
680
+
681
+ function handleRetriedAttemptFromEnvelope (runner, state) {
682
+ publishRetriedAttempt(runner, state)
683
+ startRetriedAttempt(state)
684
+ }
685
+
686
+ function publishRetriedAttempt (runner, state) {
687
+ const { promises } = state
688
+ let error
689
+ try {
690
+ const cucumberResult = runner.getWorstStepResult()
691
+ error = getErrorFromCucumberResult(cucumberResult)
692
+ } catch {
693
+ // ignore error
694
+ }
695
+
696
+ const currentAttempt = state.numAttempt
697
+ const nextAttempt = currentAttempt + 1
698
+ const failedAttemptCtx = numAttemptToCtx.get(currentAttempt)
699
+ const isFirstAttempt = currentAttempt === 0
700
+ const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
701
+
702
+ // ATR: record this attempt as failed so when run().finally runs (after retry) we have all statuses
703
+ if (isFlakyTestRetriesEnabled) {
704
+ const nameForKey = runner.pickle.name.replace(/\s*\(attempt \d+(?:, retried)?\)\s*$/, '')
705
+ const atrKey = `${runner.pickle.uri}:${nameForKey}`
706
+ if (atrStatusesByScenarioKey.has(atrKey)) {
707
+ atrStatusesByScenarioKey.get(atrKey).push('fail')
708
+ } else {
709
+ atrStatusesByScenarioKey.set(atrKey, ['fail'])
710
+ }
711
+ }
712
+
713
+ // the current span will be finished and a new one will be created
714
+ testRetryCh.publish({
715
+ isFirstAttempt,
716
+ error,
717
+ isAtrRetry,
718
+ promises,
719
+ canWaitForDi: state.testStartPayload.canWaitForDi,
720
+ ...failedAttemptCtx.currentStore,
721
+ })
722
+
723
+ state.numAttempt = nextAttempt
724
+ return promises.setProbePromise
725
+ }
726
+
727
+ function startRetriedAttempt (state) {
728
+ const { promises, testStartPayload } = state
729
+ const newCtx = { ...testStartPayload, promises }
730
+ numAttemptToCtx.set(state.numAttempt, newCtx)
731
+
732
+ testStartCh.runStores(newCtx, () => {})
733
+ }
734
+
665
735
  function wrapRun (pl, isLatestVersion, version) {
666
736
  if (patched.has(pl)) return
667
737
 
668
738
  patched.add(pl)
669
739
 
740
+ const canAwaitRetries = typeof pl.prototype.runAttempt === 'function'
741
+ if (canAwaitRetries) {
742
+ shimmer.wrap(pl.prototype, 'runAttempt', runAttempt => async function (...args) {
743
+ const willBeRetried = await runAttempt.apply(this, args)
744
+ const state = runnerToRetryState.get(this)
745
+ if (willBeRetried && state) {
746
+ await handleRetriedAttempt(this, state)
747
+ }
748
+ return willBeRetried
749
+ })
750
+ }
751
+
670
752
  shimmer.wrap(pl.prototype, 'run', run => function (...args) {
671
753
  if (!testFinishCh.hasSubscribers) {
672
754
  return run.apply(this, args)
673
755
  }
674
756
 
675
- let numAttempt = 0
676
-
677
757
  const testFileAbsolutePath = this.pickle.uri
678
758
  const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
679
759
 
@@ -684,58 +764,34 @@ function wrapRun (pl, isLatestVersion, version) {
684
764
  testFileAbsolutePath,
685
765
  testSourceLine,
686
766
  isParallel: !!getEnvironmentVariable('CUCUMBER_WORKER_ID'),
767
+ // Older Cucumber runners do not expose an awaited retry boundary. Failed Test Replay
768
+ // intentionally skips DI setup there instead of bringing back the synchronous wait.
769
+ canWaitForDi: canAwaitRetries,
687
770
  }
688
771
  const ctx = testStartPayload
689
- numAttemptToCtx.set(numAttempt, ctx)
772
+ const promises = {}
773
+ const state = { numAttempt: 0, promises, testStartPayload }
774
+ numAttemptToCtx.set(state.numAttempt, ctx)
775
+ runnerToRetryState.set(this, state)
690
776
  if (isTestManagementTestsEnabled && getTestProperties(testSuitePath, this.pickle.name).attemptToFix) {
691
777
  logAttemptToFixTestExecution(testSuitePath, this.pickle.name, loggedAttemptToFixTests)
692
778
  }
693
779
  testStartCh.runStores(ctx, () => {})
694
- const promises = {}
695
780
  try {
696
- const onEnvelope = async (testCase) => {
781
+ const onEnvelope = (testCase) => {
782
+ if (canAwaitRetries) return
783
+
697
784
  // Only supported from >=8.0.0
698
785
  if (testCase?.testCaseFinished) {
699
786
  const { testCaseFinished: { willBeRetried } } = testCase
700
787
  if (willBeRetried) { // test case failed and will be retried
701
- let error
702
- try {
703
- const cucumberResult = this.getWorstStepResult()
704
- error = getErrorFromCucumberResult(cucumberResult)
705
- } catch {
706
- // ignore error
707
- }
708
-
709
- const failedAttemptCtx = numAttemptToCtx.get(numAttempt)
710
- const isFirstAttempt = numAttempt++ === 0
711
- const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
712
-
713
- // ATR: record this attempt as failed so when run().finally runs (after retry) we have all statuses
714
- if (isFlakyTestRetriesEnabled) {
715
- const nameForKey = this.pickle.name.replace(/\s*\(attempt \d+(?:, retried)?\)\s*$/, '')
716
- const atrKey = `${this.pickle.uri}:${nameForKey}`
717
- if (atrStatusesByScenarioKey.has(atrKey)) {
718
- atrStatusesByScenarioKey.get(atrKey).push('fail')
719
- } else {
720
- atrStatusesByScenarioKey.set(atrKey, ['fail'])
721
- }
722
- }
723
-
724
- if (promises.hitBreakpointPromise) {
725
- await promises.hitBreakpointPromise
726
- }
727
-
728
- // the current span will be finished and a new one will be created
729
- testRetryCh.publish({ isFirstAttempt, error, isAtrRetry, ...failedAttemptCtx.currentStore })
730
-
731
- const newCtx = { ...testStartPayload, promises }
732
- numAttemptToCtx.set(numAttempt, newCtx)
733
-
734
- testStartCh.runStores(newCtx, () => {})
788
+ handleRetriedAttemptFromEnvelope(this, state)
735
789
  }
736
790
  }
737
791
  }
738
- this.eventBroadcaster.on('envelope', onEnvelope)
792
+ if (!canAwaitRetries) {
793
+ this.eventBroadcaster.on('envelope', onEnvelope)
794
+ }
739
795
  let promise
740
796
  const executionStart = performance.now()
741
797
 
@@ -743,7 +799,10 @@ function wrapRun (pl, isLatestVersion, version) {
743
799
  promise = run.apply(this, args)
744
800
  })
745
801
  promise.finally(async () => {
746
- this.eventBroadcaster.removeListener('envelope', onEnvelope)
802
+ if (!canAwaitRetries) {
803
+ this.eventBroadcaster.removeListener('envelope', onEnvelope)
804
+ }
805
+ runnerToRetryState.delete(this)
747
806
  const result = this.getWorstStepResult()
748
807
  const { status, skipReason } = isLatestVersion
749
808
  ? getStatusFromResultLatest(result)
@@ -851,7 +910,7 @@ function wrapRun (pl, isLatestVersion, version) {
851
910
  }
852
911
  }
853
912
 
854
- const attemptCtx = numAttemptToCtx.get(numAttempt)
913
+ const attemptCtx = numAttemptToCtx.get(state.numAttempt)
855
914
 
856
915
  const error = getErrorFromCucumberResult(result)
857
916
 
@@ -899,7 +958,7 @@ function wrapRun (pl, isLatestVersion, version) {
899
958
  error,
900
959
  isNew,
901
960
  isEfdRetry,
902
- isFlakyRetry: numAttempt > 0,
961
+ isFlakyRetry: state.numAttempt > 0,
903
962
  isAttemptToFix,
904
963
  isAttemptToFixRetry,
905
964
  hasFailedAllRetries,
@@ -113,9 +113,10 @@ function injectSupportFile (config) {
113
113
  * @param {object} config Cypress resolved config object
114
114
  * @param {Function[]} userAfterSpecHandlers user's after:spec handlers collected from wrappedOn
115
115
  * @param {Function[]} userAfterRunHandlers user's after:run handlers collected from wrappedOn
116
+ * @param {Function[]} userAfterScreenshotHandlers user's after:screenshot handlers collected from wrappedOn
116
117
  * @returns {object} the config object (possibly modified)
117
118
  */
118
- function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHandlers) {
119
+ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHandlers, userAfterScreenshotHandlers) {
119
120
  const wrapperFile = injectSupportFile(config)
120
121
 
121
122
  const cleanupWrapper = () => {
@@ -136,6 +137,7 @@ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHa
136
137
 
137
138
  const registerNoopHandlers = () => {
138
139
  for (const h of userAfterSpecHandlers) on('after:spec', h)
140
+ for (const h of userAfterScreenshotHandlers) on('after:screenshot', h)
139
141
  registerAfterRunWithCleanup()
140
142
  on('task', noopTask)
141
143
  }
@@ -153,6 +155,7 @@ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHa
153
155
  config,
154
156
  userAfterSpecHandlers,
155
157
  userAfterRunHandlers,
158
+ userAfterScreenshotHandlers,
156
159
  cleanupWrapper,
157
160
  registered: false,
158
161
  configPromise: undefined,
@@ -176,12 +179,15 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
176
179
  return function ddSetupNodeEvents (on, config) {
177
180
  const userAfterSpecHandlers = []
178
181
  const userAfterRunHandlers = []
182
+ const userAfterScreenshotHandlers = []
179
183
 
180
184
  const wrappedOn = (event, handler) => {
181
185
  if (event === 'after:spec') {
182
186
  userAfterSpecHandlers.push(handler)
183
187
  } else if (event === 'after:run') {
184
188
  userAfterRunHandlers.push(handler)
189
+ } else if (event === 'after:screenshot') {
190
+ userAfterScreenshotHandlers.push(handler)
185
191
  } else {
186
192
  on(event, handler)
187
193
  }
@@ -197,7 +203,8 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
197
203
  on,
198
204
  mergeReturnedConfig(config, result),
199
205
  userAfterSpecHandlers,
200
- userAfterRunHandlers
206
+ userAfterRunHandlers,
207
+ userAfterScreenshotHandlers
201
208
  )
202
209
  })
203
210
  }
@@ -206,7 +213,8 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
206
213
  on,
207
214
  mergeReturnedConfig(config, maybePromise),
208
215
  userAfterSpecHandlers,
209
- userAfterRunHandlers
216
+ userAfterRunHandlers,
217
+ userAfterScreenshotHandlers
210
218
  )
211
219
  }
212
220
  }
@@ -22,6 +22,8 @@ const callbackFinishCh = channel('datadog:fastify:callback:execute')
22
22
  const parsingContexts = new WeakMap()
23
23
  const cookiesPublished = new WeakSet()
24
24
  const bodyPublished = new WeakSet()
25
+ let lastPublishedError
26
+ let lastPublishedReq
25
27
 
26
28
  function wrapFastify (fastify, hasParsingEvents) {
27
29
  if (typeof fastify !== 'function') return fastify
@@ -110,16 +112,20 @@ function invokeHookWithContext (name, fn, thisArg, args) {
110
112
  const promise = fn.apply(thisArg, args)
111
113
 
112
114
  if (promise && typeof promise.catch === 'function') {
113
- return promise.catch(error => {
115
+ // Observe the rejection to publish, then hand back the original promise so
116
+ // the rejection keeps propagating untouched. Returning the handler's
117
+ // promise instead would resolve with `undefined` and swallow the rejection.
118
+ promise.catch(error => {
114
119
  ctx.error = error
115
- return publishError(ctx)
120
+ publishError(ctx)
116
121
  })
117
122
  }
118
123
 
119
124
  return promise
120
125
  } catch (error) {
121
126
  ctx.error = error
122
- throw publishError(ctx)
127
+ publishError(ctx)
128
+ throw error
123
129
  }
124
130
  }
125
131
 
@@ -305,11 +311,24 @@ function getRouteConfig (request) {
305
311
  }
306
312
 
307
313
  function publishError (ctx) {
308
- if (ctx.error) {
309
- publishErrorChannel(ctx)
310
- }
311
-
312
- return ctx.error
314
+ const error = ctx.error
315
+ if (!error) return
316
+
317
+ // avvio's boot loop (`_encapsulateThreeParam`) re-invokes the same encapsulated
318
+ // hook after it throws, re-throwing the same error object on every sequential
319
+ // re-drive (#9099), recursing the subscriber until boot overflows the stack.
320
+ // The subscribers tag once per request, so the guard collapses only a re-drive
321
+ // of the same error against the same request; a distinct request reusing a
322
+ // cached error object still publishes. Boot hooks carry no request, so their
323
+ // re-drives share the same `undefined` req and collapse after the first. The
324
+ // re-drive re-throws the one caught error on the trailing hop, so a compare
325
+ // against the previous publish bounds it without a per-error side table.
326
+ const req = ctx.req
327
+ if (error === lastPublishedError && req === lastPublishedReq) return
328
+ lastPublishedError = error
329
+ lastPublishedReq = req
330
+
331
+ publishErrorChannel(ctx)
313
332
  }
314
333
 
315
334
  function onRoute (routeOptions) {
@@ -88,8 +88,10 @@ addHook({ name: 'fs' }, fs => {
88
88
  const asyncMethods = Object.keys(paramsByMethod)
89
89
  const syncMethods = asyncMethods.map(name => `${name}Sync`)
90
90
 
91
- massWrap(fs, asyncMethods, createWrapFunction())
92
- massWrap(fs, syncMethods, createWrapFunction())
91
+ // Node 20 defines `fs.opendir` / `fs.opendirSync` as lazy accessor properties;
92
+ // `replaceGetter` resolves the method so the call is wrapped, not the accessor.
93
+ massWrap(fs, asyncMethods, createWrapFunction(), { replaceGetter: true })
94
+ massWrap(fs, syncMethods, createWrapFunction(), { replaceGetter: true })
93
95
  massWrap(fs.promises, asyncMethods, createWrapFunction('promises.'))
94
96
 
95
97
  wrap(fs.realpath, 'native', createWrapFunction('', 'realpath.native'))
@@ -355,15 +357,15 @@ function getMessage (operation, params, args, self) {
355
357
  return { operation, ...metadata }
356
358
  }
357
359
 
358
- function massWrap (target, methods, wrapper) {
360
+ function massWrap (target, methods, wrapper, options) {
359
361
  for (const method of methods) {
360
- wrap(target, method, wrapper)
362
+ wrap(target, method, wrapper, options)
361
363
  }
362
364
  }
363
365
 
364
- function wrap (target, method, wrapper) {
366
+ function wrap (target, method, wrapper, options) {
365
367
  try {
366
- shimmer.wrap(target, method, wrapper)
368
+ shimmer.wrap(target, method, wrapper, options)
367
369
  } catch {
368
370
  // skip unavailable method
369
371
  }