dd-trace 5.121.0 → 5.122.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 (137) hide show
  1. package/README.md +0 -17
  2. package/index.d.ts +108 -3
  3. package/initialize.mjs +28 -17
  4. package/openfeature.js +2 -2
  5. package/package.json +11 -8
  6. package/packages/datadog-esbuild/index.js +0 -34
  7. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  9. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  10. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  11. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  12. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  13. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  14. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  15. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  16. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  19. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  20. package/packages/datadog-instrumentations/src/jest.js +25 -17
  21. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  22. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
  24. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  25. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  26. package/packages/datadog-instrumentations/src/router.js +2 -0
  27. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  30. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  31. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  32. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  33. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  34. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  35. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  36. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  38. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  39. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  40. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  41. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  42. package/packages/datadog-plugin-electron/src/index.js +2 -0
  43. package/packages/datadog-plugin-fs/src/index.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +6 -0
  45. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  46. package/packages/datadog-plugin-nats/src/index.js +1 -1
  47. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  48. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  50. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  51. package/packages/datadog-webpack/index.js +0 -11
  52. package/packages/dd-trace/index.js +0 -1
  53. package/packages/dd-trace/src/agent/info.js +6 -5
  54. package/packages/dd-trace/src/aiguard/client.js +100 -1
  55. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  56. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  57. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  58. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  59. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  60. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  61. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  62. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  63. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  64. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  66. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  67. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  69. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  70. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  71. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  72. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  73. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  74. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  75. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  76. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  77. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  78. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  79. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  80. package/packages/dd-trace/src/config/index.js +13 -2
  81. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  82. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  83. package/packages/dd-trace/src/datastreams/index.js +2 -0
  84. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  85. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  86. package/packages/dd-trace/src/datastreams/size.js +5 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  88. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  89. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  90. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  91. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  92. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  93. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  94. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  95. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  96. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  97. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  98. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  99. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  100. package/packages/dd-trace/src/noop/proxy.js +3 -4
  101. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  102. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  103. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  104. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  105. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  106. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  107. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  108. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  109. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  110. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  111. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  112. package/packages/dd-trace/src/plugin_manager.js +3 -3
  113. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  114. package/packages/dd-trace/src/plugins/index.js +1 -0
  115. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  116. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  117. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  118. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  119. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  121. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  122. package/packages/dd-trace/src/proxy.js +38 -29
  123. package/packages/dd-trace/src/ritm.js +9 -2
  124. package/packages/dd-trace/src/serverless.js +37 -0
  125. package/packages/dd-trace/src/span_format.js +1 -0
  126. package/packages/dd-trace/src/span_stats.js +30 -3
  127. package/packages/dd-trace/src/storage-channels.js +86 -0
  128. package/packages/dd-trace/src/tracer.js +9 -2
  129. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  130. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  131. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  133. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  134. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  135. package/packages/dd-trace/src/feature-registry.js +0 -29
  136. package/packages/dd-trace/src/openfeature/register.js +0 -35
  137. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -25,6 +25,13 @@ const KNOWN_CHANNEL_SUFFIXES = new Set([
25
25
  'bedrockruntime',
26
26
  ])
27
27
 
28
+ // Older SDK clients report EventBridge under its CloudWatch Events name or its `events` endpoint
29
+ // prefix; both share the EventBridge channels.
30
+ const CHANNEL_SUFFIX_ALIASES = new Map([
31
+ ['cloudwatchevents', 'eventbridge'],
32
+ ['events', 'eventbridge'],
33
+ ])
34
+
28
35
  /**
29
36
  * @typedef {object} ChannelBag
30
37
  * @property {ReturnType<typeof channel>} start
@@ -314,7 +321,8 @@ function addResponse (ctx, error, result) {
314
321
  function getChannelSuffix (name) {
315
322
  // some resource identifiers have spaces between ex: bedrock runtime
316
323
  name = String(name).replaceAll(' ', '')
317
- return KNOWN_CHANNEL_SUFFIXES.has(name) ? name : 'default'
324
+ if (KNOWN_CHANNEL_SUFFIXES.has(name)) return name
325
+ return CHANNEL_SUFFIX_ALIASES.get(name) ?? 'default'
318
326
  }
319
327
 
320
328
  addHook({ name: '@smithy/smithy-client', versions: ['>=1.0.3'] }, smithy => {
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const wrapLogger = require('./helpers/bunyan')
4
+ const { addHook } = require('./helpers/instrument')
5
+
6
+ addHook({ name: 'browser-bunyan', versions: ['>=1'] }, browserBunyan => {
7
+ // Published versions generate `_emit` through different bundle shapes,
8
+ // so Orchestrion has no stable function to match.
9
+ wrapLogger(browserBunyan.Logger, 'browser-bunyan')
10
+ return browserBunyan
11
+ })
@@ -1,22 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const shimmer = require('../../datadog-shimmer')
4
- const {
5
- channel,
6
- addHook,
7
- } = require('./helpers/instrument')
3
+ const wrapLogger = require('./helpers/bunyan')
4
+ const { addHook } = require('./helpers/instrument')
8
5
 
9
6
  addHook({ name: 'bunyan', versions: ['>=1'] }, Logger => {
10
- const logCh = channel('apm:bunyan:log')
11
- shimmer.wrap(Logger.prototype, '_emit', emit => {
12
- return function wrappedEmit (rec) {
13
- if (logCh.hasSubscribers) {
14
- const payload = { message: rec }
15
- logCh.publish(payload)
16
- arguments[0] = payload.message
17
- }
18
- return emit.apply(this, arguments)
19
- }
20
- })
7
+ wrapLogger(Logger, 'bunyan')
21
8
  return Logger
22
9
  })
@@ -116,7 +116,7 @@ let pickleByFile = {}
116
116
  const pickleResultByFile = {}
117
117
 
118
118
  let skippableSuites = []
119
- let skippableSuitesCoverage = {}
119
+ let skippableSuitesCoverage
120
120
  let skippedSuitesCoverage = {}
121
121
  let itrCorrelationId = ''
122
122
  let isForcedToRun = false
@@ -153,12 +153,6 @@ function isValidKnownTests (receivedKnownTests) {
153
153
  return !!receivedKnownTests.cucumber
154
154
  }
155
155
 
156
- function hasSkippableSuitesCoverage () {
157
- return skippableSuitesCoverage &&
158
- typeof skippableSuitesCoverage === 'object' &&
159
- Object.keys(skippableSuitesCoverage).length > 0
160
- }
161
-
162
156
  function isTiaCoverageBackfillEnabled () {
163
157
  return isItrEnabled && isCoverageReportUploadEnabled
164
158
  }
@@ -172,7 +166,7 @@ function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
172
166
  }
173
167
 
174
168
  function getSkippedSuitesCoverageForRun () {
175
- return isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
169
+ return isSuitesSkipped && isTiaCoverageBackfillEnabled() && skippableSuitesCoverage !== undefined
176
170
  ? skippableSuitesCoverage
177
171
  : {}
178
172
  }
@@ -188,7 +182,7 @@ function getCucumberTestSessionCoverageFiles () {
188
182
 
189
183
  function resetSuiteSkippingRunState () {
190
184
  skippableSuites = []
191
- skippableSuitesCoverage = {}
185
+ skippableSuitesCoverage = undefined
192
186
  skippedSuitesCoverage = {}
193
187
  skippedSuites = []
194
188
  isSuitesSkipped = false
@@ -1143,7 +1137,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1143
1137
 
1144
1138
  errorSkippableRequest = skippableResponse.err
1145
1139
  skippableSuites = skippableResponse.skippableSuites ?? []
1146
- skippableSuitesCoverage = skippableResponse.skippableSuitesCoverage ?? {}
1140
+ skippableSuitesCoverage = skippableResponse.skippableSuitesCoverage
1147
1141
 
1148
1142
  if (!errorSkippableRequest) {
1149
1143
  const filteredPickles = isCoordinator
@@ -1222,7 +1216,28 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1222
1216
  itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
1223
1217
  }
1224
1218
 
1225
- const result = await start.apply(this, arguments)
1219
+ let result
1220
+ try {
1221
+ result = await start.apply(this, arguments)
1222
+ } catch (error) {
1223
+ try {
1224
+ await getChannelPromise(sessionFinishCh, {
1225
+ status: 'fail',
1226
+ error,
1227
+ isSuitesSkipped,
1228
+ numSkippedSuites: skippedSuites.length,
1229
+ hasUnskippableSuites: isUnskippable,
1230
+ hasForcedToRunSuites: isForcedToRun,
1231
+ isEarlyFlakeDetectionEnabled,
1232
+ isEarlyFlakeDetectionFaulty,
1233
+ isTestManagementTestsEnabled,
1234
+ isParallel,
1235
+ })
1236
+ } catch (finalizationError) {
1237
+ log.error('Cucumber test session finalization error: %s', finalizationError)
1238
+ }
1239
+ throw error
1240
+ }
1226
1241
  const success = satisfies(frameworkVersion, '>=13.1.0') ? result.success : result
1227
1242
 
1228
1243
  let untestedCoverage
@@ -7,9 +7,18 @@ const { pathToFileURL } = require('url')
7
7
 
8
8
  const log = require('../../dd-trace/src/log')
9
9
  const { getSegment } = require('../../dd-trace/src/util')
10
+ const {
11
+ finalizeAfterUserHandlers,
12
+ manualPluginOwner,
13
+ runUserHandler,
14
+ } = require('../../datadog-plugin-cypress/src/finalization')
10
15
  const { channel } = require('./helpers/instrument')
11
16
 
12
17
  const DD_CONFIG_WRAPPED = Symbol.for('dd-trace.cypress.config.wrapped')
18
+ const DD_CYPRESS_AFTER_SPEC_HANDLER = Symbol.for('dd-trace.cypress.after-spec.handler')
19
+ const DD_CYPRESS_AFTER_RUN_HANDLER = Symbol.for('dd-trace.cypress.after-run.handler')
20
+ const DD_CYPRESS_TASK_HANDLER = Symbol.for('dd-trace.cypress.task.handler')
21
+ const DD_CYPRESS_NOOP_TASK_HANDLER = Symbol.for('dd-trace.cypress.noop-task.handler')
13
22
  const BROWSER_INSTRUMENTATION_NOT_INSTALLED =
14
23
  'Browser-side Cypress Test Optimization instrumentation was not installed.'
15
24
  const CONFIG_INSTRUMENTATION_NOT_INSTALLED =
@@ -184,6 +193,47 @@ function isDatadogTaskRegistration (handler) {
184
193
  typeof handler['dd:addTags'] === 'function'
185
194
  }
186
195
 
196
+ /**
197
+ * @param {unknown} handler Cypress after:run handler
198
+ * @returns {boolean} whether this is the handler registered by the manual Datadog plugin
199
+ */
200
+ function isDatadogAfterRunHandler (handler) {
201
+ return typeof handler === 'function' && handler[DD_CYPRESS_AFTER_RUN_HANDLER] === true
202
+ }
203
+
204
+ /**
205
+ * @param {unknown} handler Cypress after:spec handler
206
+ * @returns {boolean} whether this is the handler registered by the manual Datadog plugin
207
+ */
208
+ function isDatadogAfterSpecHandler (handler) {
209
+ return typeof handler === 'function' && handler[DD_CYPRESS_AFTER_SPEC_HANDLER] === true
210
+ }
211
+
212
+ /**
213
+ * @param {object} manualPlugin manual plugin registration state
214
+ * @returns {boolean} whether the manual plugin supports error-aware finalization
215
+ */
216
+ function supportsErrorAwareFinalization (manualPlugin) {
217
+ return isDatadogAfterSpecHandler(manualPlugin.afterSpecHandler) &&
218
+ isDatadogAfterRunHandler(manualPlugin.afterRunHandler)
219
+ }
220
+
221
+ /**
222
+ * @param {unknown} handler Cypress task handler map
223
+ * @returns {boolean} whether the current manual Datadog plugin owns the task map
224
+ */
225
+ function isCurrentDatadogTaskRegistration (handler) {
226
+ return !!handler && handler[DD_CYPRESS_TASK_HANDLER] === manualPluginOwner
227
+ }
228
+
229
+ /**
230
+ * @param {unknown} handler Cypress task handler map
231
+ * @returns {boolean} whether the manual plugin registered only fallback tasks
232
+ */
233
+ function isDatadogNoopTaskRegistration (handler) {
234
+ return !!handler && handler[DD_CYPRESS_NOOP_TASK_HANDLER] === true
235
+ }
236
+
187
237
  /**
188
238
  * @param {unknown} value
189
239
  * @returns {boolean}
@@ -393,15 +443,61 @@ function registerManualAfterScreenshotHandlers (on, handlers, datadogHandler) {
393
443
  *
394
444
  * @param {Function} on Cypress event registration function
395
445
  * @param {Function[]} handlers collected after:spec handlers
446
+ * @param {Function} [datadogHandler] manual Datadog after:spec handler
447
+ * @param {Function} [cleanup] removes generated support files after an error
396
448
  * @returns {void}
397
449
  */
398
- function registerAfterSpecHandlers (on, handlers) {
399
- if (handlers.length === 0) return
450
+ function registerAfterSpecHandlers (on, handlers, datadogHandler, cleanup) {
451
+ const userHandlers = datadogHandler
452
+ ? handlers.filter(handler => handler !== datadogHandler)
453
+ : handlers
400
454
 
401
- on('after:spec', (spec, results) => handlers.reduce(
402
- (chain, handler) => chain.then(() => handler(spec, results)),
403
- Promise.resolve()
404
- ))
455
+ if (userHandlers.length === 0) {
456
+ if (datadogHandler) {
457
+ if (cleanup) {
458
+ on('after:spec', (...args) => Promise.resolve().then(() => datadogHandler(...args)).catch((error) => {
459
+ cleanup()
460
+ throw error
461
+ }))
462
+ } else {
463
+ on('after:spec', datadogHandler)
464
+ }
465
+ }
466
+ return
467
+ }
468
+
469
+ on('after:spec', (spec, results) => {
470
+ const callHandler = datadogHandler
471
+ ? handler => runUserHandler(() => handler(spec, results))
472
+ : handler => handler(spec, results)
473
+ const chain = userHandlers.reduce(
474
+ (promise, handler) => promise.then(() => callHandler(handler)),
475
+ Promise.resolve()
476
+ )
477
+ if (!datadogHandler) return chain
478
+ const finalization = finalizeAfterUserHandlers(chain, (...args) => datadogHandler(spec, results, ...args))
479
+ if (!cleanup) return finalization
480
+ return finalization.catch((error) => {
481
+ cleanup()
482
+ throw error
483
+ })
484
+ })
485
+ }
486
+
487
+ /**
488
+ * Enables the Cypress lifecycle events needed to finish Test Optimization
489
+ * sessions in interactive mode.
490
+ *
491
+ * @param {object} config Cypress resolved config object
492
+ * @returns {void}
493
+ */
494
+ function enableInteractiveRunEvents (config) {
495
+ if (config.isInteractive && config.experimentalInteractiveRunEvents !== true) {
496
+ config.experimentalInteractiveRunEvents = true
497
+ log.warn(
498
+ 'Datadog enabled Cypress experimentalInteractiveRunEvents so Test Optimization can finish the test session.'
499
+ )
500
+ }
405
501
  }
406
502
 
407
503
  /**
@@ -412,6 +508,7 @@ function registerAfterSpecHandlers (on, handlers) {
412
508
  *
413
509
  * @param {Function} on Cypress event registration function
414
510
  * @param {object} config Cypress resolved config object
511
+ * @param {Function[]} userBeforeRunHandlers user's before:run handlers collected from wrappedOn
415
512
  * @param {Function[]} userAfterSpecHandlers user's after:spec handlers collected from wrappedOn
416
513
  * @param {Function[]} userAfterRunHandlers user's after:run handlers collected from wrappedOn
417
514
  * @param {Function[]} userAfterScreenshotHandlers user's after:screenshot handlers collected from wrappedOn
@@ -421,6 +518,7 @@ function registerAfterSpecHandlers (on, handlers) {
421
518
  function registerDdTraceHooks (
422
519
  on,
423
520
  config,
521
+ userBeforeRunHandlers,
424
522
  userAfterSpecHandlers,
425
523
  userAfterRunHandlers,
426
524
  userAfterScreenshotHandlers,
@@ -433,13 +531,23 @@ function registerDdTraceHooks (
433
531
  if (generatedSupportFiles) cleanupGeneratedFiles(generatedSupportFiles)
434
532
  }
435
533
 
436
- const registerAfterRunWithCleanup = () => {
534
+ const registerAfterRunWithCleanup = (datadogHandler) => {
535
+ const handlers = datadogHandler
536
+ ? userAfterRunHandlers.filter(handler => handler !== datadogHandler)
537
+ : userAfterRunHandlers
538
+
437
539
  on('after:run', (results) => {
438
- const chain = userAfterRunHandlers.reduce(
439
- (p, h) => p.then(() => h(results)),
540
+ const callHandler = datadogHandler
541
+ ? handler => runUserHandler(() => handler(results))
542
+ : handler => handler(results)
543
+ const chain = handlers.reduce(
544
+ (promise, handler) => promise.then(() => callHandler(handler)),
440
545
  Promise.resolve()
441
546
  )
442
- return chain.finally(cleanupWrapper)
547
+ if (!datadogHandler) return chain.finally(cleanupWrapper)
548
+
549
+ return finalizeAfterUserHandlers(chain, (...args) => datadogHandler(results, ...args))
550
+ .finally(cleanupWrapper)
443
551
  })
444
552
  }
445
553
 
@@ -450,13 +558,36 @@ function registerDdTraceHooks (
450
558
  on('task', noopTask)
451
559
  }
452
560
 
453
- if (manualPlugin.detected) {
454
- registerAfterSpecHandlers(on, userAfterSpecHandlers)
561
+ if (manualPlugin.detected &&
562
+ (manualPlugin.isNoop ||
563
+ manualPlugin.ownsCurrentPlugin ||
564
+ supportsErrorAwareFinalization(manualPlugin) ||
565
+ !setupNodeEventsCh.hasSubscribers)) {
566
+ if (manualPlugin.afterRunHandler) {
567
+ enableInteractiveRunEvents(config)
568
+ if (manualPlugin.initialConfig !== config) {
569
+ manualPlugin.initialConfig.experimentalInteractiveRunEvents = config.experimentalInteractiveRunEvents
570
+ }
571
+ }
572
+ for (const handler of userBeforeRunHandlers) on('before:run', handler)
573
+ registerAfterSpecHandlers(on, userAfterSpecHandlers, manualPlugin.afterSpecHandler, cleanupWrapper)
455
574
  registerManualAfterScreenshotHandlers(on, userAfterScreenshotHandlers, manualPlugin.afterScreenshotHandler)
456
- registerAfterRunWithCleanup()
575
+ registerAfterRunWithCleanup(manualPlugin.afterRunHandler)
576
+ on('task', manualPlugin.taskHandler)
457
577
  return config
458
578
  }
459
579
 
580
+ if (manualPlugin.detected) {
581
+ userBeforeRunHandlers = userBeforeRunHandlers.filter(handler => handler !== manualPlugin.beforeRunHandler)
582
+ userAfterSpecHandlers = userAfterSpecHandlers.filter(handler => handler !== manualPlugin.afterSpecHandler)
583
+ userAfterRunHandlers = userAfterRunHandlers.filter(handler => handler !== manualPlugin.afterRunHandler)
584
+ userAfterScreenshotHandlers = userAfterScreenshotHandlers.filter(
585
+ handler => handler !== manualPlugin.afterScreenshotHandler
586
+ )
587
+ }
588
+
589
+ for (const handler of userBeforeRunHandlers) on('before:run', handler)
590
+
460
591
  if (!setupNodeEventsCh.hasSubscribers) {
461
592
  registerNoopHandlers()
462
593
  return config
@@ -483,6 +614,7 @@ function registerDdTraceHooks (
483
614
  return config
484
615
  }
485
616
 
617
+ enableInteractiveRunEvents(config)
486
618
  return payload.configPromise || config
487
619
  }
488
620
 
@@ -492,28 +624,60 @@ function registerDdTraceHooks (
492
624
  */
493
625
  function wrapSetupNodeEvents (originalSetupNodeEvents) {
494
626
  return function ddSetupNodeEvents (on, config) {
627
+ const userBeforeRunHandlers = []
495
628
  const userAfterSpecHandlers = []
496
629
  const userAfterRunHandlers = []
497
630
  const userAfterScreenshotHandlers = []
498
631
  const manualPlugin = {
499
632
  detected: false,
633
+ initialConfig: config,
634
+ beforeRunHandler: undefined,
635
+ afterSpecHandler: undefined,
636
+ afterRunHandler: undefined,
500
637
  afterScreenshotHandler: undefined,
638
+ taskHandler: undefined,
639
+ ownsCurrentPlugin: false,
640
+ isNoop: false,
501
641
  }
642
+ const recentRegistrations = []
502
643
 
503
644
  const wrappedOn = (event, handler) => {
504
- if (event === 'after:spec') {
645
+ if (event === 'before:run') {
646
+ userBeforeRunHandlers.push(handler)
647
+ } else if (event === 'after:spec') {
505
648
  userAfterSpecHandlers.push(handler)
649
+ if (isDatadogAfterSpecHandler(handler)) manualPlugin.afterSpecHandler = handler
506
650
  } else if (event === 'after:run') {
507
651
  userAfterRunHandlers.push(handler)
652
+ if (isDatadogAfterRunHandler(handler)) manualPlugin.afterRunHandler = handler
508
653
  } else if (event === 'after:screenshot') {
509
654
  userAfterScreenshotHandlers.push(handler)
510
655
  } else {
511
656
  if (event === 'task' && isDatadogTaskRegistration(handler)) {
512
657
  manualPlugin.detected = true
513
- manualPlugin.afterScreenshotHandler = userAfterScreenshotHandlers.at(-1)
658
+ manualPlugin.ownsCurrentPlugin = isCurrentDatadogTaskRegistration(handler)
659
+ manualPlugin.isNoop = isDatadogNoopTaskRegistration(handler)
660
+ const afterRun = recentRegistrations.at(-1)
661
+ const afterSpec = recentRegistrations.at(-2)
662
+ const possibleAfterScreenshot = recentRegistrations.at(-3)
663
+ const hasAfterScreenshot = possibleAfterScreenshot?.event === 'after:screenshot'
664
+ const beforeRun = hasAfterScreenshot ? recentRegistrations.at(-4) : possibleAfterScreenshot
665
+ if (!manualPlugin.isNoop &&
666
+ beforeRun?.event === 'before:run' &&
667
+ afterSpec?.event === 'after:spec' &&
668
+ afterRun?.event === 'after:run') {
669
+ manualPlugin.beforeRunHandler ||= beforeRun.handler
670
+ manualPlugin.afterSpecHandler ||= afterSpec.handler
671
+ manualPlugin.afterRunHandler ||= afterRun.handler
672
+ if (hasAfterScreenshot) manualPlugin.afterScreenshotHandler ||= possibleAfterScreenshot.handler
673
+ }
674
+ manualPlugin.taskHandler = handler
675
+ } else {
676
+ on(event, handler)
514
677
  }
515
- on(event, handler)
516
678
  }
679
+ recentRegistrations.push({ event, handler })
680
+ if (recentRegistrations.length > 4) recentRegistrations.shift()
517
681
  }
518
682
 
519
683
  const maybePromise = originalSetupNodeEvents
@@ -525,6 +689,7 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
525
689
  return registerDdTraceHooks(
526
690
  on,
527
691
  mergeReturnedConfig(config, result),
692
+ userBeforeRunHandlers,
528
693
  userAfterSpecHandlers,
529
694
  userAfterRunHandlers,
530
695
  userAfterScreenshotHandlers,
@@ -536,6 +701,7 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
536
701
  return registerDdTraceHooks(
537
702
  on,
538
703
  mergeReturnedConfig(config, maybePromise),
704
+ userBeforeRunHandlers,
539
705
  userAfterSpecHandlers,
540
706
  userAfterRunHandlers,
541
707
  userAfterScreenshotHandlers,
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const { shouldDeferLegacyFinalization } = require('../../datadog-plugin-cypress/src/finalization')
4
+ const Hook = require('./helpers/hook')
5
+
6
+ const DD_CYPRESS_LEGACY_FINALIZER_WRAPPED = Symbol.for('dd-trace.cypress.legacy-finalizer.wrapped')
7
+ const LEGACY_FINALIZER_MODULES = new Set([
8
+ 'dd-trace/packages/datadog-plugin-cypress/src/after-run.js',
9
+ 'dd-trace/packages/datadog-plugin-cypress/src/after-spec.js',
10
+ ])
11
+
12
+ /**
13
+ * @param {Function} finalizer standalone Datadog Cypress finalizer
14
+ * @returns {Function} context-aware finalizer
15
+ */
16
+ function wrapLegacyFinalizer (finalizer) {
17
+ if (finalizer[DD_CYPRESS_LEGACY_FINALIZER_WRAPPED]) return finalizer
18
+
19
+ function wrappedLegacyFinalizer () {
20
+ if (shouldDeferLegacyFinalization()) return
21
+ return finalizer.apply(this, arguments)
22
+ }
23
+ wrappedLegacyFinalizer[DD_CYPRESS_LEGACY_FINALIZER_WRAPPED] = true
24
+ return wrappedLegacyFinalizer
25
+ }
26
+
27
+ Hook(['dd-trace'], { internals: true }, (moduleExports, moduleName) => {
28
+ if (!LEGACY_FINALIZER_MODULES.has(moduleName.replaceAll('\\', '/'))) return moduleExports
29
+ return wrapLegacyFinalizer(moduleExports)
30
+ })
@@ -8,6 +8,8 @@ const {
8
8
  wrapConfig,
9
9
  } = require('./cypress-config')
10
10
 
11
+ require('./cypress-legacy-finalizers')
12
+
11
13
  const MINIMUM_CYPRESS_VERSION = DD_MAJOR >= 6 ? '>=12.0.0' : '>=10.2.0'
12
14
 
13
15
  // Wrap defineConfig() so configs are instrumented when loaded in Cypress's
@@ -146,6 +146,7 @@ function wrapHookDone (ctx, request, reply, req, name, doneCallback) {
146
146
  ctx.error = error
147
147
  publishError(ctx)
148
148
 
149
+ // eslint-disable-next-line no-restricted-syntax -- arbitrary cookie names; publishing {} sets a WAF address
149
150
  const hasCookies = request.cookies && Object.keys(request.cookies).length > 0
150
151
 
151
152
  if (cookieParserReadCh.hasSubscribers && hasCookies && !cookiesPublished.has(req)) {
@@ -193,6 +194,7 @@ function preHandler (request, reply, done) {
193
194
  const res = getRes(reply)
194
195
  const ctx = { req, res }
195
196
 
197
+ // eslint-disable-next-line no-restricted-syntax -- arbitrary body keys; publishing {} sets a WAF address
196
198
  const hasBody = request.body && Object.keys(request.body).length > 0
197
199
 
198
200
  // For multipart/form-data, the body is not available until after preValidation hook
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../../datadog-shimmer')
4
+ const { channel } = require('./instrument')
5
+
6
+ /**
7
+ * @param {{ prototype: object }} Logger
8
+ * @param {string} id
9
+ */
10
+ module.exports = function wrapLogger (Logger, id) {
11
+ const logCh = channel(`apm:${id}:log`)
12
+ shimmer.wrap(Logger.prototype, '_emit', emit => {
13
+ return function wrappedEmit (rec) {
14
+ if (logCh.hasSubscribers) {
15
+ const payload = { message: rec }
16
+ logCh.publish(payload)
17
+ arguments[0] = payload.message
18
+ }
19
+ return emit.apply(this, arguments)
20
+ }
21
+ })
22
+ }
@@ -70,6 +70,7 @@ module.exports = {
70
70
  'aws-sdk': () => require('../aws-sdk'),
71
71
  bluebird: () => require('../bluebird'),
72
72
  'body-parser': () => require('../body-parser'),
73
+ 'browser-bunyan': () => require('../browser-bunyan'),
73
74
  bullmq: () => require('../bullmq'),
74
75
  bunyan: () => require('../bunyan'),
75
76
  'cassandra-driver': () => require('../cassandra-driver'),
@@ -110,7 +111,7 @@ module.exports = {
110
111
  'light-my-request': () => require('../light-my-request'),
111
112
  'limitd-client': () => require('../limitd-client'),
112
113
  lodash: () => require('../lodash'),
113
- mariadb: () => require('../mariadb'),
114
+ mariadb: { esmFirst: true, fn: () => require('../mariadb') },
114
115
  memcached: () => require('../memcached'),
115
116
  mercurius: () => require('../mercurius'),
116
117
  'microgateway-core': () => require('../microgateway-core'),
@@ -518,10 +518,11 @@ function runPoolQueryConnectionCallback (
518
518
  * @param {unknown} error
519
519
  * @param {Record<string, unknown>} acquireCtx
520
520
  * @param {AcquireErrorChannels} channels
521
+ * @param {number} [poolWaitTime]
521
522
  */
522
- function reportPoolAcquireError (start, error, acquireCtx, channels) {
523
+ function reportPoolAcquireError (start, error, acquireCtx, channels, poolWaitTime) {
523
524
  acquireCtx.error = error
524
- acquireCtx.poolWaitTime = acquireWait(start)
525
+ acquireCtx.poolWaitTime = poolWaitTime ?? acquireWait(start)
525
526
  if (start !== undefined) acquireCtx.startTime = performance.timeOrigin + start
526
527
  channels.acquireStartCh.publish(acquireCtx)
527
528
  channels.acquireFinishCh.publish(acquireCtx)
@@ -536,11 +537,14 @@ function reportPoolAcquireError (start, error, acquireCtx, channels) {
536
537
  * @param {Function} callback
537
538
  * @param {unknown} thisArg
538
539
  * @param {ArgumentsLike} args
540
+ * @param {number} [poolWaitTime]
539
541
  * @returns {unknown}
540
542
  */
541
- function runPoolAcquireError (start, error, acquireCtx, channels, connectionCtx, callback, thisArg, args) {
543
+ function runPoolAcquireError (
544
+ start, error, acquireCtx, channels, connectionCtx, callback, thisArg, args, poolWaitTime
545
+ ) {
542
546
  return channels.connectionFinishCh.runStores(connectionCtx, () => {
543
- reportPoolAcquireError(start, error, acquireCtx, channels)
547
+ reportPoolAcquireError(start, error, acquireCtx, channels, poolWaitTime)
544
548
  return callback.apply(thisArg, args)
545
549
  })
546
550
  }
@@ -613,6 +617,7 @@ function takeClusterAcquire (key) {
613
617
  }
614
618
 
615
619
  module.exports = {
620
+ acquireWait,
616
621
  clearPoolWaitTime,
617
622
  dispatchesAcquireSynchronously,
618
623
  isPoolQueryAcquire,
@@ -112,14 +112,6 @@ for (const name of names) {
112
112
  }
113
113
 
114
114
  for (const { file, versions, hook, filePattern, patchDefault } of instrumentations[name]) {
115
- if (isIitm && patchDefault === !!moduleExports.default) {
116
- if (patchDefault) {
117
- moduleExports = moduleExports.default
118
- } else {
119
- return moduleExports
120
- }
121
- }
122
-
123
115
  const fullFilename = filename(name, file)
124
116
 
125
117
  let matchesFile = moduleName === fullFilename
@@ -134,6 +126,16 @@ for (const name of names) {
134
126
  }
135
127
 
136
128
  if (matchesFile && matchVersion(moduleVersion, versions)) {
129
+ // IITM invokes this callback for every module in the package. Only unwrap the namespace after its file and
130
+ // version match, otherwise a default export from an unrelated internal module can replace that module.
131
+ if (isIitm && patchDefault === !!moduleExports.default) {
132
+ if (patchDefault) {
133
+ moduleExports = moduleExports.default
134
+ } else {
135
+ return moduleExports
136
+ }
137
+ }
138
+
137
139
  // Do not log in case of an error to prevent duplicate telemetry for the same integration version.
138
140
  instrumentedIntegrationsSuccess.set(`${name}@${moduleVersion}`, true)
139
141
  try {