dd-trace 6.10.0 → 6.12.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 (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const { AsyncLocalStorage } = require('node:async_hooks')
4
+
5
+ const log = require('../../dd-trace/src/log')
6
+
7
+ const DD_CYPRESS_USER_HANDLER_CONTEXT = Symbol.for('dd-trace.cypress.user-handler.context')
8
+ const userHandlerContext = globalThis[DD_CYPRESS_USER_HANDLER_CONTEXT] ||= new AsyncLocalStorage()
9
+ const manualPluginOwner = {}
10
+
11
+ /**
12
+ * Runs a Cypress user handler while allowing wrapped legacy Datadog helpers to
13
+ * defer to the managed finalizer.
14
+ *
15
+ * @param {() => unknown} handler user handler invocation
16
+ * @returns {unknown} handler result
17
+ */
18
+ function runUserHandler (handler) {
19
+ return userHandlerContext.run(true, handler)
20
+ }
21
+
22
+ /**
23
+ * @returns {boolean} whether a managed finalizer owns the current lifecycle event
24
+ */
25
+ function shouldDeferLegacyFinalization () {
26
+ return userHandlerContext.getStore() === true
27
+ }
28
+
29
+ /**
30
+ * Converts a Cypress rejection reason into error metadata without changing the
31
+ * value rethrown to Cypress.
32
+ *
33
+ * @param {unknown} reason rejection reason
34
+ * @returns {Error} error used for Test Optimization finalization
35
+ */
36
+ function normalizeUserHandlerError (reason) {
37
+ if (reason instanceof Error) return reason
38
+ if (reason == null) return new Error('Cypress user handler rejected without an error')
39
+
40
+ try {
41
+ return new Error(String(reason))
42
+ } catch {
43
+ return new Error('Cypress user handler failed')
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Runs the Datadog finalizer after user handlers while preserving the user
49
+ * error as the framework-visible failure.
50
+ *
51
+ * @param {Promise<void>} userHandlers collected user-handler chain
52
+ * @param {(userError?: unknown) => unknown} finalizer Datadog finalizer
53
+ * @returns {Promise<unknown>} finalizer result
54
+ */
55
+ function finalizeAfterUserHandlers (userHandlers, finalizer) {
56
+ return userHandlers.then(
57
+ () => finalizer(),
58
+ userError => Promise.resolve().then(() => {
59
+ return finalizer(normalizeUserHandlerError(userError))
60
+ }).then(
61
+ () => { throw userError },
62
+ finalizerError => {
63
+ log.error('Datadog Cypress finalizer failed after a user handler error', finalizerError)
64
+ throw userError
65
+ }
66
+ )
67
+ )
68
+ }
69
+
70
+ module.exports = {
71
+ finalizeAfterUserHandlers,
72
+ manualPluginOwner,
73
+ normalizeUserHandlerError,
74
+ runUserHandler,
75
+ shouldDeferLegacyFinalization,
76
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const NoopTracer = require('../../dd-trace/src/noop/tracer')
4
4
  const Plugin = require('../../dd-trace/src/plugins/plugin')
5
+ const { finalizeAfterUserHandlers, runUserHandler } = require('./finalization')
5
6
 
6
7
  /**
7
8
  * Cypress plugin handles setup-node-events from the instrumentation layer
@@ -30,11 +31,12 @@ class CypressPlugin extends Plugin {
30
31
  const registerAfterRunWithCleanup = (afterRunHandler) => {
31
32
  on('after:run', (results) => {
32
33
  const chain = userAfterRunHandlers.reduce(
33
- (p, h) => p.then(() => h(results)),
34
+ (p, h) => p.then(() => runUserHandler(() => h(results))),
34
35
  Promise.resolve()
35
36
  )
36
37
  if (afterRunHandler) {
37
- return chain.then(() => afterRunHandler(results)).finally(cleanupWrapper)
38
+ return finalizeAfterUserHandlers(chain, userError => afterRunHandler(results, userError))
39
+ .finally(cleanupWrapper)
38
40
  }
39
41
  return chain.finally(cleanupWrapper)
40
42
  })
@@ -82,7 +84,7 @@ class CypressPlugin extends Plugin {
82
84
  // (the chained registration replaces the one plugin.js set, so it must include it).
83
85
  if (userAfterSpecHandlers.length > 0) {
84
86
  on('after:spec', (spec, results) => userAfterSpecHandlers.reduce(
85
- (chain, handler) => chain.then(() => handler(spec, results)),
87
+ (chain, handler) => chain.then(() => runUserHandler(() => handler(spec, results))),
86
88
  Promise.resolve()
87
89
  ))
88
90
  }
@@ -97,13 +99,17 @@ class CypressPlugin extends Plugin {
97
99
 
98
100
  on('after:spec', (spec, results) => {
99
101
  const chain = userAfterSpecHandlers.reduce(
100
- (p, h) => p.then(() => h(spec, results)),
102
+ (p, h) => p.then(() => runUserHandler(() => h(spec, results))),
101
103
  Promise.resolve()
102
104
  )
103
- return chain.then(() => cypressPlugin.afterSpec(spec, results))
105
+ return finalizeAfterUserHandlers(chain, userError => cypressPlugin.afterSpec(spec, results, userError))
106
+ .catch((error) => {
107
+ cleanupWrapper()
108
+ throw error
109
+ })
104
110
  })
105
111
 
106
- registerAfterRunWithCleanup((results) => cypressPlugin.afterRun(results))
112
+ registerAfterRunWithCleanup((results, userError) => cypressPlugin.afterRun(results, userError))
107
113
 
108
114
  on('task', cypressPlugin.getTasks())
109
115
 
@@ -4,6 +4,12 @@ const NoopTracer = require('../../dd-trace/src/noop/tracer')
4
4
  const satisfies = require('../../../vendor/dist/semifies')
5
5
  const { DD_MAJOR } = require('../../../version')
6
6
  const cypressPlugin = require('./cypress-plugin')
7
+ const { manualPluginOwner } = require('./finalization')
8
+
9
+ const DD_CYPRESS_AFTER_SPEC_HANDLER = Symbol.for('dd-trace.cypress.after-spec.handler')
10
+ const DD_CYPRESS_AFTER_RUN_HANDLER = Symbol.for('dd-trace.cypress.after-run.handler')
11
+ const DD_CYPRESS_TASK_HANDLER = Symbol.for('dd-trace.cypress.task.handler')
12
+ const DD_CYPRESS_NOOP_TASK_HANDLER = Symbol.for('dd-trace.cypress.noop-task.handler')
7
13
 
8
14
  const noopTask = {
9
15
  'dd:testSuiteStart': () => {
@@ -18,6 +24,7 @@ const noopTask = {
18
24
  'dd:addTags': () => {
19
25
  return null
20
26
  },
27
+ [DD_CYPRESS_NOOP_TASK_HANDLER]: true,
21
28
  }
22
29
 
23
30
  module.exports = function CypressPlugin (on, config) {
@@ -41,9 +48,15 @@ module.exports = function CypressPlugin (on, config) {
41
48
 
42
49
  on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
43
50
  on('after:screenshot', cypressPlugin.getAfterScreenshotHandler())
44
- on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin))
45
- on('after:run', cypressPlugin.afterRun.bind(cypressPlugin))
46
- on('task', cypressPlugin.getTasks())
51
+ const afterSpecHandler = cypressPlugin.afterSpec.bind(cypressPlugin)
52
+ afterSpecHandler[DD_CYPRESS_AFTER_SPEC_HANDLER] = true
53
+ on('after:spec', afterSpecHandler)
54
+ const afterRunHandler = cypressPlugin.afterRun.bind(cypressPlugin)
55
+ afterRunHandler[DD_CYPRESS_AFTER_RUN_HANDLER] = true
56
+ on('after:run', afterRunHandler)
57
+ const taskHandler = cypressPlugin.getTasks()
58
+ taskHandler[DD_CYPRESS_TASK_HANDLER] = manualPluginOwner
59
+ on('task', taskHandler)
47
60
 
48
61
  return cypressPlugin.init(tracer, config)
49
62
  }
@@ -78,6 +78,10 @@ module.exports = class DdTraceApiPlugin extends Plugin {
78
78
  }
79
79
 
80
80
  // handleEvent('configure')
81
+ // No handoff for `openfeature` yet: an application that calls into the tracer
82
+ // exclusively through the `dd-trace-api` shim (instead of `require('dd-trace')`) cannot
83
+ // reach `tracer.openfeature` through that shim. This does not affect SSI itself --
84
+ // auto-injection and vendored-provider resolution work regardless of this gap.
81
85
  handleEvent('startSpan')
82
86
  handleEvent('wrap')
83
87
  handleEvent('trace')
@@ -1,11 +1,13 @@
1
1
  'use strict'
2
2
 
3
+ const { DD_MAJOR } = require('../../../version')
3
4
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
5
  const ElectronIpcPlugin = require('./ipc')
5
6
  const ElectronNetPlugin = require('./net')
6
7
 
7
8
  class ElectronPlugin extends CompositePlugin {
8
9
  static id = 'electron'
10
+ static optIn = DD_MAJOR >= 7
9
11
  static get plugins () {
10
12
  return {
11
13
  net: ElectronNetPlugin,
@@ -5,7 +5,7 @@ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
5
  class FsPlugin extends TracingPlugin {
6
6
  static id = 'fs'
7
7
  static operation = 'operation'
8
- static experimental = true
8
+ static optIn = true
9
9
 
10
10
  bindStart (ctx) {
11
11
  if (!this.activeSpan) return { noop: true }
@@ -113,6 +113,7 @@ class JestPlugin extends CiPlugin {
113
113
  hasUnskippableSuites,
114
114
  hasForcedToRunSuites,
115
115
  error,
116
+ isTestSessionFinalizationError,
116
117
  isEarlyFlakeDetectionEnabled,
117
118
  isEarlyFlakeDetectionFaulty,
118
119
  isTestManagementTestsEnabled,
@@ -123,6 +124,9 @@ class JestPlugin extends CiPlugin {
123
124
  this.testModuleSpan.setTag(TEST_STATUS, status)
124
125
 
125
126
  if (error) {
127
+ if (isTestSessionFinalizationError) {
128
+ this.tracer._exporter.setDeferredTestSuiteError?.(error)
129
+ }
126
130
  this.testSessionSpan.setTag('error', error)
127
131
  this.testModuleSpan.setTag('error', error)
128
132
  }
@@ -159,6 +163,7 @@ class JestPlugin extends CiPlugin {
159
163
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
160
164
  }
161
165
 
166
+ this.tracer._exporter.exportDeferredTestSuiteSpans?.()
162
167
  this.testModuleSpan.finish()
163
168
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
164
169
  this.testSessionSpan.finish()
@@ -329,6 +334,7 @@ class JestPlugin extends CiPlugin {
329
334
  this.pendingTestSuiteFinishes.add(pendingFinish)
330
335
 
331
336
  const finish = () => {
337
+ this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
332
338
  testSuiteSpan.finish()
333
339
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
334
340
  // Suites potentially run in a different process than the session,
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { performance } = require('node:perf_hooks')
4
+ const dateNow = Date.now
4
5
  const { fileURLToPath } = require('node:url')
5
6
 
6
7
  const { channel } = require('dc-polyfill')
@@ -173,6 +174,15 @@ class MochaPlugin extends CiPlugin {
173
174
 
174
175
  this._testTitleToParams = {}
175
176
  this.sourceRoot = process.cwd()
177
+ this._pendingTestSuiteSpans = []
178
+ this._timeOrigin = dateNow()
179
+ this._perfOrigin = performance.now()
180
+
181
+ this.addSub('ci:mocha:session:start', () => {
182
+ this._pendingTestSuiteSpans = []
183
+ this._timeOrigin = dateNow()
184
+ this._perfOrigin = performance.now()
185
+ })
176
186
 
177
187
  this.addSub('ci:mocha:worker:configuration', ({
178
188
  libraryConfig,
@@ -472,7 +482,19 @@ class MochaPlugin extends CiPlugin {
472
482
  if (!testSuiteSpan.context().getTag(TEST_STATUS)) {
473
483
  testSuiteSpan.setTag(TEST_STATUS, status)
474
484
  }
475
- testSuiteSpan.finish()
485
+ const exporter = this.tracer._exporter
486
+ if (exporter.deferTestSuiteSpan) {
487
+ exporter.deferTestSuiteSpan(testSuiteSpan)
488
+ testSuiteSpan.finish()
489
+ } else if (exporter.exportDeferredTestSuiteSpans) {
490
+ const finishTime = this._now()
491
+ this._pendingTestSuiteSpans.push({
492
+ span: testSuiteSpan,
493
+ finishTime,
494
+ })
495
+ } else {
496
+ testSuiteSpan.finish()
497
+ }
476
498
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
477
499
  }
478
500
  })
@@ -522,6 +544,7 @@ class MochaPlugin extends CiPlugin {
522
544
  })
523
545
 
524
546
  this.addSub('ci:mocha:worker:finish', ({ onDone } = {}) => {
547
+ this._finishPendingTestSuiteSpans()
525
548
  this.tracer._exporter.flush(onDone)
526
549
  })
527
550
 
@@ -715,6 +738,7 @@ class MochaPlugin extends CiPlugin {
715
738
  isEarlyFlakeDetectionFaulty,
716
739
  isTestManagementEnabled,
717
740
  isParallel,
741
+ isFrameworkError,
718
742
  onDone,
719
743
  }) => {
720
744
  this._exportPendingWorkerTraces()
@@ -730,8 +754,17 @@ class MochaPlugin extends CiPlugin {
730
754
  if (error) {
731
755
  this.testSessionSpan.setTag('error', error)
732
756
  this.testModuleSpan.setTag('error', error)
757
+ if (isFrameworkError) {
758
+ this.tracer._exporter.setDeferredTestSuiteError?.(error)
759
+ for (const testSuiteSpan of this._testSuiteSpansByTestSuite.values()) {
760
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
761
+ testSuiteSpan.setTag('error', error)
762
+ }
763
+ }
733
764
  }
734
765
 
766
+ this._finishPendingTestSuiteSpans()
767
+
735
768
  if (isParallel) {
736
769
  this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
737
770
  }
@@ -769,6 +802,7 @@ class MochaPlugin extends CiPlugin {
769
802
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
770
803
  }
771
804
 
805
+ this.tracer._exporter.exportDeferredTestSuiteSpans?.()
772
806
  this.testModuleSpan.finish()
773
807
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
774
808
  this.testSessionSpan.finish()
@@ -790,6 +824,27 @@ class MochaPlugin extends CiPlugin {
790
824
  })
791
825
  }
792
826
 
827
+ /**
828
+ * Returns the current time in the coordinate system used by spans.
829
+ *
830
+ * @returns {number}
831
+ */
832
+ _now () {
833
+ return this._timeOrigin + performance.now() - this._perfOrigin
834
+ }
835
+
836
+ /**
837
+ * Finishes suites retained for a later framework finalization event.
838
+ *
839
+ * @returns {void}
840
+ */
841
+ _finishPendingTestSuiteSpans () {
842
+ for (const { span, finishTime } of this._pendingTestSuiteSpans) {
843
+ span.finish(finishTime)
844
+ }
845
+ this._pendingTestSuiteSpans = []
846
+ }
847
+
793
848
  /**
794
849
  * Starts a Jasmine test span around WebdriverIO's test-function wrapper.
795
850
  *
@@ -8,7 +8,7 @@ class NatsPlugin extends CompositePlugin {
8
8
  static id = 'nats'
9
9
  // Disabled by default — users must opt in via DD_TRACE_NATS_ENABLED=true
10
10
  // or `tracer.use('nats')`. Matches the feature parity dashboard policy.
11
- static experimental = true
11
+ static optIn = true
12
12
  static get plugins () {
13
13
  return {
14
14
  producer: ProducerPlugin,
@@ -102,23 +102,23 @@ class OpenAiTracingPlugin extends TracingPlugin {
102
102
  },
103
103
  }, false)
104
104
 
105
- const openaiStore = Object.create(null)
106
-
107
105
  const tags = {} // The remaining tags are added one at a time
108
106
 
109
107
  if (payload.stream) {
110
108
  tags['openai.request.stream'] = payload.stream
111
109
  }
112
110
 
111
+ let openaiStore
112
+
113
113
  switch (normalizedMethodName) {
114
114
  case 'createImage':
115
115
  case 'createImageEdit':
116
116
  case 'createImageVariation':
117
- commonCreateImageRequestExtraction(tags, payload, openaiStore)
117
+ openaiStore = commonCreateImageRequestExtraction(tags, payload)
118
118
  break
119
119
 
120
120
  case 'createChatCompletion':
121
- createChatCompletionRequestExtraction(tags, payload, openaiStore)
121
+ openaiStore = createChatCompletionRequestExtraction(tags, payload)
122
122
  break
123
123
 
124
124
  case 'createFile':
@@ -128,7 +128,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
128
128
 
129
129
  case 'createTranscription':
130
130
  case 'createTranslation':
131
- commonCreateAudioRequestExtraction(tags, payload, openaiStore)
131
+ openaiStore = commonCreateAudioRequestExtraction(tags, payload)
132
132
  break
133
133
 
134
134
  case 'retrieveModel':
@@ -136,11 +136,11 @@ class OpenAiTracingPlugin extends TracingPlugin {
136
136
  break
137
137
 
138
138
  case 'createEdit':
139
- createEditRequestExtraction(tags, payload, openaiStore)
139
+ openaiStore = createEditRequestExtraction(tags, payload)
140
140
  break
141
141
 
142
142
  case 'createResponse':
143
- createResponseRequestExtraction(tags, payload, openaiStore)
143
+ openaiStore = createResponseRequestExtraction(tags, payload)
144
144
  break
145
145
  }
146
146
 
@@ -177,8 +177,6 @@ class OpenAiTracingPlugin extends TracingPlugin {
177
177
 
178
178
  body = coerceResponseBody(body, normalizedMethodName)
179
179
 
180
- const openaiStore = store.openai
181
-
182
180
  if (!error && (path?.startsWith('https://') || path?.startsWith('http://'))) {
183
181
  // basic checking for if the path was set as a full URL
184
182
  // not using a full regex as it will likely be "https://api.openai.com/..."
@@ -204,7 +202,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
204
202
  'openai.response.created_at': body.created_at,
205
203
  }
206
204
 
207
- responseDataExtractionByMethod(normalizedMethodName, tags, body, openaiStore)
205
+ const openaiStore = responseDataExtractionByMethod(normalizedMethodName, tags, body, store.openai)
208
206
  span.addTags(tags)
209
207
 
210
208
  span.finish()
@@ -298,7 +296,6 @@ class OpenAiTracingPlugin extends TracingPlugin {
298
296
 
299
297
  sendLog (methodName, span, tags, openaiStore, error) {
300
298
  if (!openaiStore) return
301
- if (!Object.keys(openaiStore).length) return
302
299
  if (!this.sampler.isSampled(span)) return
303
300
 
304
301
  const log = {
@@ -395,45 +392,54 @@ function normalizeMethodName (methodName) {
395
392
  }
396
393
  }
397
394
 
398
- function createEditRequestExtraction (tags, payload, openaiStore) {
399
- const instruction = payload.instruction
400
- openaiStore.instruction = instruction
395
+ function createEditRequestExtraction (tags, payload) {
396
+ const openaiStore = Object.create(null)
397
+ openaiStore.instruction = payload.instruction
398
+ return openaiStore
401
399
  }
402
400
 
403
- function createResponseRequestExtraction (tags, payload, openaiStore) {
401
+ function createResponseRequestExtraction (tags, payload) {
404
402
  // Extract model information
405
403
  if (payload.model) {
406
404
  tags['openai.request.model'] = payload.model
407
405
  }
408
406
 
409
407
  // Store the full payload for response extraction
408
+ const openaiStore = Object.create(null)
410
409
  openaiStore.responseData = payload
410
+ return openaiStore
411
411
  }
412
412
 
413
413
  function retrieveModelRequestExtraction (tags, payload) {
414
414
  tags['openai.request.id'] = payload.id
415
415
  }
416
416
 
417
- function createChatCompletionRequestExtraction (tags, payload, openaiStore) {
417
+ function createChatCompletionRequestExtraction (tags, payload) {
418
418
  const messages = payload.messages
419
419
  if (!defensiveArrayLength(messages)) return
420
420
 
421
- openaiStore.messages = payload.messages
421
+ const openaiStore = Object.create(null)
422
+ openaiStore.messages = messages
423
+ return openaiStore
422
424
  }
423
425
 
424
- function commonCreateImageRequestExtraction (tags, payload, openaiStore) {
426
+ function commonCreateImageRequestExtraction (tags, payload) {
427
+ let openaiStore
428
+
425
429
  // createImageEdit, createImageVariation
426
430
  const img = payload.file || payload.image
427
431
  if (img !== null && typeof img === 'object' && img.path) {
428
- const file = path.basename(img.path)
429
- openaiStore.file = file
432
+ openaiStore = Object.create(null)
433
+ openaiStore.file = path.basename(img.path)
430
434
  }
431
435
 
432
436
  // createImageEdit
433
437
  if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) {
434
- const mask = path.basename(payload.mask.path)
435
- openaiStore.mask = mask
438
+ openaiStore ??= Object.create(null)
439
+ openaiStore.mask = path.basename(payload.mask.path)
436
440
  }
441
+
442
+ return openaiStore
437
443
  }
438
444
 
439
445
  function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
@@ -441,12 +447,10 @@ function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
441
447
  case 'createCompletion':
442
448
  case 'createChatCompletion':
443
449
  case 'createEdit':
444
- commonCreateResponseExtraction(tags, body, openaiStore, methodName)
445
- break
450
+ return commonCreateResponseExtraction(tags, body, openaiStore, methodName)
446
451
 
447
452
  case 'createResponse':
448
- createResponseResponseExtraction(tags, body, openaiStore)
449
- break
453
+ return createResponseResponseExtraction(tags, body, openaiStore)
450
454
 
451
455
  case 'listFiles':
452
456
  case 'listFineTunes':
@@ -475,6 +479,8 @@ function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
475
479
  retrieveModelResponseExtraction(tags, body)
476
480
  break
477
481
  }
482
+
483
+ return openaiStore
478
484
  }
479
485
 
480
486
  function retrieveModelResponseExtraction (tags, body) {
@@ -513,10 +519,11 @@ function deleteFileResponseExtraction (tags, body) {
513
519
  tags['openai.response.id'] = body.id
514
520
  }
515
521
 
516
- function commonCreateAudioRequestExtraction (tags, body, openaiStore) {
522
+ function commonCreateAudioRequestExtraction (tags, body) {
517
523
  if (body.file !== null && typeof body.file === 'object' && body.file.path) {
518
- const filename = path.basename(body.file.path)
519
- openaiStore.file = filename
524
+ const openaiStore = Object.create(null)
525
+ openaiStore.file = path.basename(body.file.path)
526
+ return openaiStore
520
527
  }
521
528
  }
522
529
 
@@ -546,9 +553,11 @@ function commonListCountResponseExtraction (tags, body) {
546
553
 
547
554
  // createCompletion, createChatCompletion, createEdit
548
555
  function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
549
- if (!body.choices) return
556
+ if (!body.choices) return openaiStore
550
557
 
558
+ openaiStore ??= Object.create(null)
551
559
  openaiStore.choices = body.choices
560
+ return openaiStore
552
561
  }
553
562
 
554
563
  function createResponseResponseExtraction (tags, body, openaiStore) {
@@ -568,7 +577,9 @@ function createResponseResponseExtraction (tags, body, openaiStore) {
568
577
  }
569
578
 
570
579
  // Store the full response for potential future use
580
+ openaiStore ??= Object.create(null)
571
581
  openaiStore.response = body
582
+ return openaiStore
572
583
  }
573
584
 
574
585
  // The server almost always responds with JSON
@@ -498,6 +498,7 @@ class OpenAIAgentsIntegration {
498
498
 
499
499
  this.#tagger.tagTextIO(ddSpan, inputValue, outputValue)
500
500
 
501
+ // eslint-disable-next-line no-restricted-syntax -- agents-core builds metadata before the plugin receives it
501
502
  if (info.metadata && Object.keys(info.metadata).length > 0) {
502
503
  this.#tagger.tagMetadata(ddSpan, info.metadata)
503
504
  }