dd-trace 6.17.0 → 6.19.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +55 -11
  4. package/package.json +6 -6
  5. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  6. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +50 -9
  8. package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
  9. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
  12. package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
  19. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  20. package/packages/datadog-instrumentations/src/jest.js +30 -13
  21. package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
  23. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
  24. package/packages/datadog-instrumentations/src/playwright.js +73 -6
  25. package/packages/datadog-instrumentations/src/undici.js +7 -3
  26. package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
  27. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  28. package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
  29. package/packages/datadog-instrumentations/src/ws.js +41 -1
  30. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  31. package/packages/datadog-plugin-cucumber/src/index.js +22 -8
  32. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
  33. package/packages/datadog-plugin-cypress/src/index.js +3 -1
  34. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  35. package/packages/datadog-plugin-jest/src/index.js +3 -4
  36. package/packages/datadog-plugin-mocha/src/index.js +3 -4
  37. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  38. package/packages/datadog-plugin-playwright/src/index.js +11 -6
  39. package/packages/datadog-plugin-undici/src/index.js +5 -0
  40. package/packages/datadog-plugin-vitest/src/index.js +3 -4
  41. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  42. package/packages/datadog-shimmer/src/shimmer.js +14 -10
  43. package/packages/datadog-turbopack/index.js +4 -1
  44. package/packages/datadog-turbopack/src/loader.js +6 -2
  45. package/packages/dd-trace/src/agent/info.js +5 -4
  46. package/packages/dd-trace/src/aiguard/client.js +38 -19
  47. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  48. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  49. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  50. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  51. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  52. package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
  53. package/packages/dd-trace/src/config/index.js +7 -6
  54. package/packages/dd-trace/src/config/supported-configurations.json +16 -5
  55. package/packages/dd-trace/src/constants.js +2 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  57. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  60. package/packages/dd-trace/src/debugger/index.js +1 -1
  61. package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
  62. package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
  63. package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
  64. package/packages/dd-trace/src/evp_proxy/path.js +21 -7
  65. package/packages/dd-trace/src/exporters/common/url.js +33 -2
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
  67. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  68. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  70. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  72. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  73. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  74. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  75. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  76. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  77. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  78. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  79. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
  80. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  81. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  82. package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
  83. package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
  84. package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
  85. package/packages/dd-trace/src/llmobs/tagger.js +11 -40
  86. package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
  87. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
  88. package/packages/dd-trace/src/openfeature/index.js +6 -2
  89. package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
  90. package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
  91. package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
  92. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  93. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  94. package/packages/dd-trace/src/plugin_manager.js +27 -8
  95. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +13 -4
  97. package/packages/dd-trace/src/profiler.js +51 -5
  98. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  99. package/packages/dd-trace/src/proxy.js +2 -2
  100. package/packages/dd-trace/src/ritm.js +12 -9
  101. package/packages/dd-trace/src/span_processor.js +6 -1
  102. package/packages/dd-trace/src/telemetry/send-data.js +2 -1
  103. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +5 -4
  105. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  106. package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
  107. package/packages/datadog-instrumentations/src/bullmq.js +0 -11
  108. package/packages/datadog-instrumentations/src/langchain.js +0 -7
  109. package/packages/datadog-instrumentations/src/langgraph.js +0 -7
  110. package/packages/datadog-instrumentations/src/mercurius.js +0 -11
@@ -426,15 +426,16 @@ function registerManualAfterScreenshotHandlers (on, handlers, datadogHandler) {
426
426
  }
427
427
 
428
428
  /**
429
- * Registers one Cypress after:spec handler that runs every collected handler
429
+ * Registers one Cypress lifecycle handler that runs every collected handler
430
430
  * in registration order. Cypress 10+ otherwise keeps only the last handler.
431
431
  *
432
432
  * @param {Function} on Cypress event registration function
433
- * @param {Function[]} handlers collected after:spec handlers
434
- * @param {Function} [datadogHandler] manual Datadog after:spec handler
433
+ * @param {'before:run'|'after:spec'} event lifecycle event
434
+ * @param {Function[]} handlers collected lifecycle handlers
435
+ * @param {Function} [datadogHandler] Datadog lifecycle handler
435
436
  * @param {Function} [cleanup] removes generated support files after an error
436
437
  */
437
- function registerAfterSpecHandlers (on, handlers, datadogHandler, cleanup) {
438
+ function registerLifecycleHandlers (on, event, handlers, datadogHandler, cleanup) {
438
439
  const userHandlers = datadogHandler
439
440
  ? handlers.filter(handler => handler !== datadogHandler)
440
441
  : handlers
@@ -442,27 +443,31 @@ function registerAfterSpecHandlers (on, handlers, datadogHandler, cleanup) {
442
443
  if (userHandlers.length === 0) {
443
444
  if (datadogHandler) {
444
445
  if (cleanup) {
445
- on('after:spec', (...args) => Promise.resolve().then(() => datadogHandler(...args)).catch((error) => {
446
+ on(event, (...args) => Promise.resolve().then(() => datadogHandler(...args)).catch((error) => {
446
447
  cleanup()
447
448
  throw error
448
449
  }))
449
450
  } else {
450
- on('after:spec', datadogHandler)
451
+ on(event, datadogHandler)
451
452
  }
452
453
  }
453
454
  return
454
455
  }
455
456
 
456
- on('after:spec', (spec, results) => {
457
+ on(event, (...args) => {
457
458
  const callHandler = datadogHandler
458
- ? handler => runUserHandler(() => handler(spec, results))
459
- : handler => handler(spec, results)
459
+ ? handler => runUserHandler(() => handler(...args))
460
+ : handler => handler(...args)
460
461
  const chain = userHandlers.reduce(
461
462
  (promise, handler) => promise.then(() => callHandler(handler)),
462
463
  Promise.resolve()
463
464
  )
464
465
  if (!datadogHandler) return chain
465
- const finalization = finalizeAfterUserHandlers(chain, (...args) => datadogHandler(spec, results, ...args))
466
+ const finalization = finalizeAfterUserHandlers(chain, (...errors) => {
467
+ // A failed setup hook must abort the run before Datadog starts its session.
468
+ if (event === 'before:run' && errors.length > 0) return
469
+ return datadogHandler(...args, ...errors)
470
+ })
466
471
  if (!cleanup) return finalization
467
472
  return finalization.catch((error) => {
468
473
  cleanup()
@@ -514,6 +519,9 @@ function registerDdTraceHooks (
514
519
  if (generatedSupportFiles) registerGeneratedFilesForExitCleanup(generatedSupportFiles)
515
520
 
516
521
  const cleanupWrapper = () => {
522
+ // Open mode reuses this support file across runs, including failed setup attempts.
523
+ // The registered process-exit cleanup owns its lifetime instead.
524
+ if (config.isInteractive) return
517
525
  if (generatedSupportFiles) cleanupGeneratedFiles(generatedSupportFiles)
518
526
  }
519
527
 
@@ -537,8 +545,13 @@ function registerDdTraceHooks (
537
545
  })
538
546
  }
539
547
 
548
+ const registerBeforeRun = handler => {
549
+ registerLifecycleHandlers(on, 'before:run', userBeforeRunHandlers, handler, cleanupWrapper)
550
+ }
551
+
540
552
  const registerNoopHandlers = () => {
541
- registerAfterSpecHandlers(on, userAfterSpecHandlers)
553
+ registerBeforeRun()
554
+ registerLifecycleHandlers(on, 'after:spec', userAfterSpecHandlers)
542
555
  for (const h of userAfterScreenshotHandlers) on('after:screenshot', h)
543
556
  registerAfterRunWithCleanup()
544
557
  on('task', noopTask)
@@ -555,8 +568,8 @@ function registerDdTraceHooks (
555
568
  manualPlugin.initialConfig.experimentalInteractiveRunEvents = config.experimentalInteractiveRunEvents
556
569
  }
557
570
  }
558
- for (const handler of userBeforeRunHandlers) on('before:run', handler)
559
- registerAfterSpecHandlers(on, userAfterSpecHandlers, manualPlugin.afterSpecHandler, cleanupWrapper)
571
+ registerBeforeRun(manualPlugin.beforeRunHandler)
572
+ registerLifecycleHandlers(on, 'after:spec', userAfterSpecHandlers, manualPlugin.afterSpecHandler, cleanupWrapper)
560
573
  registerManualAfterScreenshotHandlers(on, userAfterScreenshotHandlers, manualPlugin.afterScreenshotHandler)
561
574
  registerAfterRunWithCleanup(manualPlugin.afterRunHandler)
562
575
  on('task', manualPlugin.taskHandler)
@@ -572,8 +585,6 @@ function registerDdTraceHooks (
572
585
  )
573
586
  }
574
587
 
575
- for (const handler of userBeforeRunHandlers) on('before:run', handler)
576
-
577
588
  if (!setupNodeEventsCh.hasSubscribers) {
578
589
  registerNoopHandlers()
579
590
  return config
@@ -588,6 +599,7 @@ function registerDdTraceHooks (
588
599
  userAfterSpecHandlers,
589
600
  userAfterRunHandlers,
590
601
  userAfterScreenshotHandlers,
602
+ registerBeforeRun,
591
603
  cleanupWrapper,
592
604
  registered: false,
593
605
  configPromise: undefined,
@@ -13,6 +13,7 @@ const {
13
13
  } = require('./instrumentation-utils')
14
14
  const hooks = require('./hooks')
15
15
  const instrumentations = require('./instrumentations')
16
+ const { isRewriteActivationEnabled } = require('./rewriter/targets')
16
17
  const disabledInstrumentations = getDisabledInstrumentations()
17
18
 
18
19
  // register.js has now set up ritm (require-in-the-middle). In bundled
@@ -94,6 +95,11 @@ dc.subscribe(CHANNEL, (message) => {
94
95
  const name = payload.package
95
96
  if (disabledInstrumentations.has(name)) return
96
97
 
98
+ if (payload.activate && isRewriteActivationEnabled(name)) {
99
+ loadChannel.publish({ name })
100
+ return
101
+ }
102
+
97
103
  const isPrefixedWithNode = name.startsWith('node:')
98
104
  const isNodeModule = isPrefixedWithNode || !hooks[name]
99
105
 
@@ -69,6 +69,7 @@ const earlyLoadFrameworks = new Map([
69
69
  */
70
70
  module.exports.checkForRequiredModules = function () {
71
71
  const packages = require('./hooks')
72
+ const { isRewriteTargetName } = require('./rewriter/targets')
72
73
  const naughties = new Set()
73
74
  const frameworksSeen = new Set()
74
75
  let didWarn = false
@@ -98,7 +99,7 @@ module.exports.checkForRequiredModules = function () {
98
99
  continue
99
100
  }
100
101
 
101
- if (naughties.has(pkg) || !(pkg in packages)) continue
102
+ if (naughties.has(pkg) || (!(pkg in packages) && !isRewriteTargetName(pkg))) continue
102
103
 
103
104
  loadOrderWarnings.push(
104
105
  () => `Warning: Package '${pkg}' was loaded before dd-trace! This may break instrumentation.`
@@ -25,14 +25,12 @@ module.exports = {
25
25
  '@anthropic-ai/sdk': { esmFirst: true, fn: () => require('../anthropic') },
26
26
  '@apollo/server': () => require('../apollo-server'),
27
27
  '@apollo/gateway': () => require('../apollo'),
28
- '@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
29
28
  '@modelcontextprotocol/sdk': { esmFirst: true, fn: () => require('../modelcontextprotocol-sdk') },
30
29
  '@openai/agents': () => require('../openai-agents'),
31
30
  '@openai/agents-openai': () => require('../openai-agents'),
32
31
  'apollo-server-core': () => require('../apollo-server-core'),
33
32
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
34
33
  '@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
35
- '@azure/cosmos': { esmFirst: true, fn: () => require('../azure-cosmos') },
36
34
  '@azure/event-hubs': () => require('../azure-event-hubs'),
37
35
  '@azure/functions': () => require('../azure-functions'),
38
36
  'durable-functions': () => require('../azure-durable-functions'),
@@ -54,7 +52,6 @@ module.exports = {
54
52
  '@jest/test-sequencer': () => require('../jest'),
55
53
  '@jest/transform': () => require('../jest'),
56
54
  '@koa/router': () => require('../koa'),
57
- '@langchain/core': { esmFirst: true, fn: () => require('../langchain') },
58
55
  '@node-redis/client': () => require('../redis'),
59
56
  '@opensearch-project/opensearch': () => require('../opensearch'),
60
57
  '@opentelemetry/sdk-trace': () => require('../otel-sdk-trace'),
@@ -78,7 +75,6 @@ module.exports = {
78
75
  bluebird: () => require('../bluebird'),
79
76
  'body-parser': () => require('../body-parser'),
80
77
  'browser-bunyan': () => require('../browser-bunyan'),
81
- bullmq: () => require('../bullmq'),
82
78
  bunyan: () => require('../bunyan'),
83
79
  'cassandra-driver': () => require('../cassandra-driver'),
84
80
  connect: () => require('../connect'),
@@ -114,14 +110,12 @@ module.exports = {
114
110
  'koa-router': () => require('../koa'),
115
111
  '@confluentinc/kafka-javascript': () => require('../confluentinc-kafka-javascript'),
116
112
  kafkajs: () => require('../kafkajs'),
117
- langchain: () => require('../langchain'),
118
113
  ldapjs: () => require('../ldapjs'),
119
114
  'light-my-request': () => require('../light-my-request'),
120
115
  'limitd-client': () => require('../limitd-client'),
121
116
  lodash: () => require('../lodash'),
122
117
  mariadb: { esmFirst: true, fn: () => require('../mariadb') },
123
118
  memcached: () => require('../memcached'),
124
- mercurius: () => require('../mercurius'),
125
119
  'microgateway-core': () => require('../microgateway-core'),
126
120
  mocha: () => require('../mocha'),
127
121
  'mocha-each': () => require('../mocha'),
@@ -25,6 +25,7 @@ const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
25
25
  const disabledInstrumentations = getDisabledInstrumentations()
26
26
 
27
27
  const loadChannel = channel('dd-trace:instrumentation:load')
28
+ const orchestrionLoadChannel = channel('dd-trace:instrumentation:load:orchestrion')
28
29
 
29
30
  // Globals
30
31
  if (!disabledInstrumentations.has('fetch')) {
@@ -55,6 +56,25 @@ const instrumentedNodeModules = new Map()
55
56
  const instrumentedIntegrationsSuccess = new Map()
56
57
  /** @type {Set<string>} */
57
58
  const alreadyLoggedIncompatibleIntegrations = new Set()
59
+ /** @type {Set<string>} */
60
+ const compatibleOrchestrionTargets = new Set()
61
+
62
+ orchestrionLoadChannel.subscribe(({ moduleName, version, result }) => {
63
+ const nameVersion = `${moduleName}@${version}`
64
+
65
+ if (result === 'unsupported') {
66
+ if (!compatibleOrchestrionTargets.has(nameVersion) && !instrumentedIntegrationsSuccess.has(nameVersion)) {
67
+ instrumentedIntegrationsSuccess.set(nameVersion, false)
68
+ }
69
+ return
70
+ }
71
+
72
+ if (result === 'matched' || result === 'rewritten') {
73
+ compatibleOrchestrionTargets.add(nameVersion)
74
+ instrumentedIntegrationsSuccess.set(nameVersion, true)
75
+ if (result === 'rewritten') loadChannel.publish({ name: moduleName })
76
+ }
77
+ })
58
78
 
59
79
  for (const name of names) {
60
80
  if (disabledInstrumentations.has(name)) continue
@@ -19,7 +19,7 @@ const { getRewriteTarget } = require('./targets')
19
19
  * { code: string, map?: string|object }} transform
20
20
  *
21
21
  * @typedef {(content: string|Buffer|ArrayBuffer|Uint8Array, filename: string, format?: string,
22
- * target?: { moduleName: string, filePath: string }, sourceMap?: string|object) =>
22
+ * target?: { moduleName: string, filePath: string, activate?: boolean }, sourceMap?: string|object) =>
23
23
  * { code: string|Buffer|ArrayBuffer|Uint8Array, map?: string|object }} BundlerRewriter
24
24
  */
25
25
 
@@ -62,7 +62,7 @@ function normalizeFilename (filename) {
62
62
  * @param {string|Buffer|ArrayBuffer|Uint8Array} content
63
63
  * @param {string} filename
64
64
  * @param {string} [format]
65
- * @param {{ moduleName: string, filePath: string }} [target]
65
+ * @param {{ moduleName: string, filePath: string, activate?: boolean }} [target]
66
66
  * @returns {string|Buffer|ArrayBuffer|Uint8Array}
67
67
  */
68
68
  function rewrite (content, filename, format, target) {
@@ -74,7 +74,7 @@ function rewrite (content, filename, format, target) {
74
74
  if (!target) return content
75
75
 
76
76
  const moduleType = format === 'module' ? 'esm' : 'cjs'
77
- const { moduleName, filePath } = target
77
+ const { moduleName, filePath, activate } = target
78
78
  if (disabled.has(moduleName)) return content
79
79
 
80
80
  const version = getVersion(filename, filePath)
@@ -82,10 +82,18 @@ function rewrite (content, filename, format, target) {
82
82
 
83
83
  const transformer = getMatcher(moduleType).getTransformer(moduleName, version, filePath)
84
84
 
85
- if (!transformer) return content
85
+ if (!transformer && !activate) return content
86
86
 
87
87
  const source = getSourceText(content)
88
88
 
89
+ if (!transformer) {
90
+ return appendOrchestrionLoad(
91
+ source,
92
+ { moduleName, version, result: 'unsupported' },
93
+ moduleType
94
+ )
95
+ }
96
+
89
97
  // TODO: pass existing sourcemap as input for remapping
90
98
  let { code, map } = transformer.transform(source, moduleType)
91
99
 
@@ -98,6 +106,11 @@ function rewrite (content, filename, format, target) {
98
106
  map = shiftSourceMapLine(map)
99
107
  }
100
108
 
109
+ if (activate) {
110
+ const result = code === source ? 'matched' : 'rewritten'
111
+ code = appendOrchestrionLoad(code, { moduleName, version, result }, moduleType)
112
+ }
113
+
101
114
  if (!map) return code
102
115
 
103
116
  const inlineMap = Buffer.from(typeof map === 'string' ? map : JSON.stringify(map)).toString('base64')
@@ -236,6 +249,30 @@ function getSourceText (source) {
236
249
  return Buffer.from(source).toString('utf8')
237
250
  }
238
251
 
252
+ /**
253
+ * Publish target-level compatibility on the application thread when a pure target is evaluated.
254
+ *
255
+ * @param {string} source
256
+ * @param {{ moduleName: string, version: string,
257
+ * result: 'unsupported'|'matched'|'rewritten' }} payload
258
+ * @param {'cjs'|'esm'} moduleType
259
+ */
260
+ function appendOrchestrionLoad (source, payload, moduleType) {
261
+ const dcModule = JSON.stringify(getDcPolyfillSpecifier(moduleType) ?? 'diagnostics_channel')
262
+ const message = JSON.stringify(payload)
263
+
264
+ if (moduleType === 'esm') {
265
+ let binding = 'ddTraceOrchestrionDc'
266
+ let suffix = 0
267
+ while (source.includes(binding)) binding = `ddTraceOrchestrionDc${++suffix}`
268
+
269
+ return `${source};import ${binding} from ${dcModule};` +
270
+ `${binding}.channel('dd-trace:instrumentation:load:orchestrion').publish(${message});`
271
+ }
272
+
273
+ return `${source};require(${dcModule}).channel('dd-trace:instrumentation:load:orchestrion').publish(${message});`
274
+ }
275
+
239
276
  /**
240
277
  * Account for a shebang restored ahead of the generated program. The transformer already maps original positions
241
278
  * past the input shebang, so only the generated side needs an additional empty line.
@@ -1,17 +1,19 @@
1
1
  'use strict'
2
2
 
3
- // activationName is the plugin-manager key published after a target is successfully rewritten.
3
+ // Activated rewrites publish their module name to the plugin manager after evaluation.
4
+ // Only pure Orchestrion integrations that need this activation set activate: true.
5
+ // Other entries use hooks or another activation path, or do not need activation from a rewrite.
4
6
  const registry = [
5
7
  { instrumentations: require('./instrumentations/ai') },
6
- { activationName: '@azure/cosmos', instrumentations: require('./instrumentations/azure-cosmos') },
8
+ { activate: true, instrumentations: require('./instrumentations/azure-cosmos') },
7
9
  { instrumentations: require('./instrumentations/azure-durable-functions') },
8
- { activationName: 'bullmq', instrumentations: require('./instrumentations/bullmq') },
10
+ { activate: true, instrumentations: require('./instrumentations/bullmq') },
9
11
  { instrumentations: require('./instrumentations/claude-agent-sdk') },
10
12
  { instrumentations: require('./instrumentations/graphql') },
11
13
  { instrumentations: require('./instrumentations/graphql-jit') },
12
- { activationName: '@langchain/core', instrumentations: require('./instrumentations/langchain') },
13
- { activationName: '@langchain/langgraph', instrumentations: require('./instrumentations/langgraph') },
14
- { activationName: 'mercurius', instrumentations: require('./instrumentations/mercurius') },
14
+ { activate: true, instrumentations: require('./instrumentations/langchain') },
15
+ { activate: true, instrumentations: require('./instrumentations/langgraph') },
16
+ { activate: true, instrumentations: require('./instrumentations/mercurius') },
15
17
  { instrumentations: require('./instrumentations/modelcontextprotocol-sdk') },
16
18
  { instrumentations: require('./instrumentations/openai-agents') },
17
19
  { instrumentations: require('./instrumentations/playwright') },
@@ -21,10 +23,10 @@ const registry = [
21
23
  { instrumentations: require('./instrumentations/supabase') },
22
24
  ]
23
25
 
24
- const activationNames = new Map()
25
- for (const { activationName, instrumentations } of registry) {
26
- if (!activationName) continue
27
- for (const { module } of instrumentations) activationNames.set(module.name, activationName)
26
+ const activatedModules = new Set()
27
+ for (const { activate, instrumentations } of registry) {
28
+ if (!activate) continue
29
+ for (const { module } of instrumentations) activatedModules.add(module.name)
28
30
  }
29
31
 
30
32
  const instrumentations = registry.flatMap(entry => entry.instrumentations)
@@ -32,8 +34,8 @@ const instrumentations = registry.flatMap(entry => entry.instrumentations)
32
34
  /**
33
35
  * @param {string} moduleName
34
36
  */
35
- function getRewriteActivationName (moduleName) {
36
- return activationNames.get(moduleName)
37
+ function isRewriteActivationEnabled (moduleName) {
38
+ return activatedModules.has(moduleName)
37
39
  }
38
40
 
39
- module.exports = { getRewriteActivationName, instrumentations, registry }
41
+ module.exports = { isRewriteActivationEnabled, instrumentations, registry }
@@ -3,6 +3,57 @@
3
3
  // Playwright keeps several hook targets in private local classes/functions.
4
4
  // Keep these rewrites limited to bundled internals that addHook cannot wrap.
5
5
  module.exports = [
6
+ ...[
7
+ ['>=1.38.0 <1.60.0', 'lib/common/config.js'],
8
+ ['>=1.60.0', 'lib/common/index.js'],
9
+ ].map(([versionRange, filePath]) => ({
10
+ module: { name: 'playwright', versionRange, filePath },
11
+ functionQuery: { className: 'FullProjectInternal', methodName: 'constructor', kind: 'Sync' },
12
+ channelName: 'FullProjectInternal',
13
+ })),
14
+ {
15
+ module: {
16
+ name: '@playwright/test',
17
+ versionRange: '>=1.18.0 <1.38.0',
18
+ filePath: 'lib/reporters/multiplexer.js',
19
+ },
20
+ functionQuery: {
21
+ className: 'Multiplexer',
22
+ methodName: 'onError',
23
+ kind: 'Sync',
24
+ },
25
+ channelName: 'Multiplexer_onError',
26
+ },
27
+ {
28
+ module: {
29
+ name: 'playwright',
30
+ versionRange: '>=1.44.0 <1.60.0',
31
+ filePath: 'lib/runner/runner.js',
32
+ },
33
+ astQuery: 'FunctionDeclaration[id.name="writeLastRunInfo"] ' +
34
+ 'CallExpression[callee.property.name="map"] > ArrowFunctionExpression',
35
+ channelName: 'lastRunTestId',
36
+ },
37
+ {
38
+ module: {
39
+ name: 'playwright',
40
+ versionRange: '>=1.44.0 <1.60.0',
41
+ filePath: 'lib/runner/lastRun.js',
42
+ },
43
+ astQuery: 'ClassDeclaration[id.name="LastRunReporter"] ' +
44
+ 'CallExpression[callee.property.name="map"] > ArrowFunctionExpression',
45
+ channelName: 'lastRunTestId',
46
+ },
47
+ {
48
+ module: {
49
+ name: 'playwright',
50
+ versionRange: '>=1.60.0',
51
+ filePath: 'lib/runner/index.js',
52
+ },
53
+ astQuery: 'VariableDeclarator[id.name="LastRunReporter"] ' +
54
+ 'CallExpression[callee.property.name="map"] > ArrowFunctionExpression',
55
+ channelName: 'lastRunTestId',
56
+ },
6
57
  {
7
58
  module: {
8
59
  name: 'playwright',
@@ -1,6 +1,19 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = [
4
+ {
5
+ module: {
6
+ name: '@wdio/config',
7
+ versionRange: '>=9.0.0',
8
+ filePath: 'build/node/index.js',
9
+ },
10
+ functionQuery: {
11
+ className: 'ConfigParser',
12
+ methodName: 'shard',
13
+ kind: 'Sync',
14
+ },
15
+ channelName: 'ConfigParser_shard',
16
+ },
4
17
  {
5
18
  module: {
6
19
  name: '@wdio/cli',
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
- // Generated by scripts/generate-rewriter-targets.js from ./instrumentations. Do not edit by hand.
3
+ // targets.json is generated by scripts/generate-rewriter-targets.js from ./instrumentation-registry.
4
4
  const targets = require('./targets.json')
5
+ const rewriteTargetNames = new Set(Object.values(targets))
5
6
 
6
7
  const NODE_MODULES = '/node_modules/'
7
8
 
@@ -9,6 +10,7 @@ const NODE_MODULES = '/node_modules/'
9
10
  * @typedef {object} RewriteTarget
10
11
  * @property {string} moduleName
11
12
  * @property {string} filePath
13
+ * @property {boolean} [activate]
12
14
  */
13
15
 
14
16
  /**
@@ -20,13 +22,36 @@ function getRewriteTarget (filename) {
20
22
  if (nodeModulesIndex === -1) return
21
23
 
22
24
  const modulePath = filename.slice(nodeModulesIndex + NODE_MODULES.length)
25
+ if (!Object.hasOwn(targets, modulePath)) return
23
26
  const moduleName = targets[modulePath]
24
27
  if (typeof moduleName !== 'string') return
28
+ const activate = isRewriteActivationEnabled(moduleName)
25
29
 
26
- return {
30
+ const rewriteTarget = {
27
31
  moduleName,
28
32
  filePath: modulePath.slice(moduleName.length + 1),
29
33
  }
34
+ if (activate) rewriteTarget.activate = true
35
+ return rewriteTarget
30
36
  }
31
37
 
32
- module.exports = { getRewriteTarget }
38
+ function getRewriteTargetNames () {
39
+ return rewriteTargetNames.values()
40
+ }
41
+
42
+ /**
43
+ * @param {string} name
44
+ */
45
+ function isRewriteTargetName (name) {
46
+ return rewriteTargetNames.has(name)
47
+ }
48
+
49
+ /**
50
+ * @param {string} moduleName
51
+ */
52
+ function isRewriteActivationEnabled (moduleName) {
53
+ // Keep target-name-only consumers from loading every instrumentation descriptor.
54
+ return require('./instrumentation-registry').isRewriteActivationEnabled(moduleName)
55
+ }
56
+
57
+ module.exports = { getRewriteTarget, getRewriteTargetNames, isRewriteActivationEnabled, isRewriteTargetName }
@@ -30,6 +30,7 @@
30
30
  "@modelcontextprotocol/sdk/dist/esm/server/mcp.js": "@modelcontextprotocol/sdk",
31
31
  "@openai/agents-openai/dist/openaiChatCompletionsModel.js": "@openai/agents-openai",
32
32
  "@openai/agents-openai/dist/openaiChatCompletionsModel.mjs": "@openai/agents-openai",
33
+ "@playwright/test/lib/reporters/multiplexer.js": "@playwright/test",
33
34
  "@supabase/auth-js/dist/main/GoTrueClient.js": "@supabase/auth-js",
34
35
  "@supabase/auth-js/dist/module/GoTrueClient.js": "@supabase/auth-js",
35
36
  "@supabase/functions-js/dist/main/FunctionsClient.js": "@supabase/functions-js",
@@ -41,6 +42,7 @@
41
42
  "@supabase/storage-js/dist/index.cjs": "@supabase/storage-js",
42
43
  "@supabase/storage-js/dist/index.mjs": "@supabase/storage-js",
43
44
  "@wdio/cli/build/index.js": "@wdio/cli",
45
+ "@wdio/config/build/node/index.js": "@wdio/config",
44
46
  "@wdio/jasmine-framework/build/index.js": "@wdio/jasmine-framework",
45
47
  "@wdio/local-runner/build/index.js": "@wdio/local-runner",
46
48
  "@wdio/runner/build/index.js": "@wdio/runner",
@@ -75,8 +77,12 @@
75
77
  "jasmine-core/lib/jasmine-core/jasmine.js": "jasmine-core",
76
78
  "mercurius/index.js": "mercurius",
77
79
  "playwright-core/lib/coreBundle.js": "playwright-core",
80
+ "playwright/lib/common/config.js": "playwright",
81
+ "playwright/lib/common/index.js": "playwright",
78
82
  "playwright/lib/index.js": "playwright",
79
83
  "playwright/lib/runner/index.js": "playwright",
84
+ "playwright/lib/runner/lastRun.js": "playwright",
85
+ "playwright/lib/runner/runner.js": "playwright",
80
86
  "postgres/cjs/src/connection.js": "postgres",
81
87
  "postgres/cjs/src/index.js": "postgres",
82
88
  "postgres/cjs/src/query.js": "postgres",
@@ -0,0 +1,102 @@
1
+ 'use strict'
2
+
3
+ const { stripVTControlCharacters } = require('node:util')
4
+
5
+ const { MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION: MAX_LENGTH } =
6
+ require('../../../dd-trace/src/encode/tags-processors')
7
+ const log = require('../../../dd-trace/src/log')
8
+
9
+ /**
10
+ * @typedef {{ name?: unknown, type?: unknown, message?: unknown, stack?: unknown }} SuiteError
11
+ * @typedef {object} JestResults
12
+ * @property {number} [numFailedTestSuites]
13
+ * @property {number} [numFailedTests]
14
+ * @property {{ testExecError?: SuiteError, failureMessage?: unknown, numFailingTests?: number }[]} [testResults]
15
+ */
16
+
17
+ /**
18
+ * Includes complete, deduplicated suite errors only when all details fit.
19
+ *
20
+ * @param {JestResults} results
21
+ * @returns {Error}
22
+ */
23
+ function getSessionError (results = {}) {
24
+ const { numFailedTestSuites = 0, numFailedTests = 0 } = results
25
+ const summary = `Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`
26
+ const error = new Error(summary)
27
+ const groups = new Map()
28
+ let size = 0
29
+ try {
30
+ const { testResults } = results
31
+ if (!testResults) return error
32
+ for (const result of testResults) {
33
+ let name, message, stack
34
+ try {
35
+ const { testExecError } = result
36
+ if (testExecError && !result.numFailingTests) {
37
+ name = readField(testExecError.name)
38
+ if (name === '') name = readField(testExecError.type)
39
+ message = readField(testExecError.message)
40
+ stack = readField(testExecError.stack)
41
+ } else {
42
+ // Jest's report includes assertion failures, hook errors and any suite execution error.
43
+ name = 'Error'
44
+ message = readField(result.failureMessage)
45
+ stack = message
46
+ }
47
+ } catch {
48
+ log.debug('Skipping unreadable Jest suite error in the test session summary')
49
+ continue
50
+ }
51
+ if (name === undefined || message === undefined || stack === undefined) return omitDetails(error)
52
+ if (!message && !stack) continue
53
+ name ||= 'Error'
54
+ const key = JSON.stringify([name, message, stack])
55
+ const group = groups.get(key)
56
+ if (group) {
57
+ group.count++
58
+ } else {
59
+ // Retained text must fit across error.message and error.stack; also bound grouping memory.
60
+ size += name.length + message.length + stack.length
61
+ if (size > 2 * MAX_LENGTH) return omitDetails(error)
62
+ groups.set(key, { name, message, stack, count: 1 })
63
+ }
64
+ }
65
+ } catch {
66
+ log.debug('Unable to read Jest results for the test session summary')
67
+ return error
68
+ }
69
+ if (!groups.size) return error
70
+
71
+ const errors = [...groups.keys()].sort().map(key => groups.get(key))
72
+ let message = summary
73
+ let stack = ''
74
+ for (const { name, message: detail, stack: originalStack, count } of errors) {
75
+ message += `\n\n${name}: ${detail} (${count} suite${count === 1 ? '' : 's'})`
76
+ const prefix = errors.length === 1 ? '' : `Affected suites: ${count}\n`
77
+ stack += (stack ? '\n\n' : '') + prefix + (originalStack || `${name}: ${detail}`)
78
+ if (message.length > MAX_LENGTH || stack.length > MAX_LENGTH) return omitDetails(error)
79
+ }
80
+ error.name = errors.length === 1 ? errors[0].name : 'Error'
81
+ error.message = message
82
+ error.stack = stack
83
+ return error
84
+ }
85
+
86
+ /**
87
+ * Returns undefined for oversized input before scanning or copying it.
88
+ * @param {unknown} value
89
+ */
90
+ function readField (value) {
91
+ if (typeof value !== 'string') return ''
92
+ if (value.length > MAX_LENGTH) return
93
+ return stripVTControlCharacters(value)
94
+ }
95
+
96
+ /** @param {Error} error */
97
+ function omitDetails (error) {
98
+ error.message += '\n\nError details exceed the size limit. See test and suite events for full details.'
99
+ return error
100
+ }
101
+
102
+ module.exports = { getSessionError }