dd-trace 5.114.0 → 5.116.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 (207) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/index.electron.js +3 -0
  52. package/init.js +18 -0
  53. package/initialize.mjs +1 -1
  54. package/loader-hook.mjs +28 -18
  55. package/openfeature.d.ts +1 -0
  56. package/openfeature.js +4 -0
  57. package/package.json +15 -8
  58. package/packages/datadog-instrumentations/src/ai.js +37 -26
  59. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  60. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  61. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  62. package/packages/datadog-instrumentations/src/cucumber.js +24 -10
  63. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  64. package/packages/datadog-instrumentations/src/express.js +20 -2
  65. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  66. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  68. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  69. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  70. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  71. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  72. package/packages/datadog-instrumentations/src/jest.js +36 -4
  73. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  74. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  75. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  76. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  77. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  78. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  79. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  80. package/packages/datadog-instrumentations/src/next.js +24 -0
  81. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  82. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  83. package/packages/datadog-instrumentations/src/pg.js +2 -2
  84. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  85. package/packages/datadog-instrumentations/src/process.js +3 -0
  86. package/packages/datadog-instrumentations/src/router.js +191 -106
  87. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  88. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  89. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  90. package/packages/datadog-instrumentations/src/ws.js +2 -1
  91. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  92. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  93. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  94. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  95. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  96. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
  97. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  98. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
  99. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  100. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  101. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  102. package/packages/datadog-plugin-graphql/src/execute.js +177 -59
  103. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  104. package/packages/datadog-plugin-graphql/src/request.js +32 -1
  105. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  106. package/packages/datadog-plugin-graphql/src/validate.js +15 -3
  107. package/packages/datadog-plugin-http2/src/client.js +1 -1
  108. package/packages/datadog-plugin-http2/src/server.js +38 -6
  109. package/packages/datadog-plugin-jest/src/util.js +21 -6
  110. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  111. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  112. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  113. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  114. package/packages/datadog-plugin-router/src/index.js +11 -2
  115. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  116. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  117. package/packages/datadog-plugin-ws/src/close.js +2 -1
  118. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  119. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  120. package/packages/datadog-plugin-ws/src/server.js +2 -1
  121. package/packages/dd-trace/index.electron.js +3 -0
  122. package/packages/dd-trace/index.js +2 -37
  123. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  125. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  126. package/packages/dd-trace/src/azure_metadata.js +2 -1
  127. package/packages/dd-trace/src/bootstrap.js +39 -0
  128. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  129. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  131. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  132. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  135. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  137. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  138. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
  139. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
  140. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  141. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  142. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  143. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  144. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  145. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
  146. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  147. package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
  148. package/packages/dd-trace/src/config/helper.js +1 -0
  149. package/packages/dd-trace/src/config/index.js +10 -0
  150. package/packages/dd-trace/src/config/supported-configurations.json +83 -0
  151. package/packages/dd-trace/src/constants.js +7 -0
  152. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  153. package/packages/dd-trace/src/exporter.js +2 -0
  154. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  155. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  156. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  157. package/packages/dd-trace/src/feature-registry.js +29 -0
  158. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  159. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  160. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  161. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  162. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  163. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  164. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  165. package/packages/dd-trace/src/llmobs/index.js +10 -2
  166. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  167. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  168. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  169. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  170. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  171. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  172. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  173. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  174. package/packages/dd-trace/src/llmobs/tagger.js +79 -7
  175. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  176. package/packages/dd-trace/src/llmobs/util.js +32 -0
  177. package/packages/dd-trace/src/noop/proxy.js +5 -4
  178. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  179. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  180. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  181. package/packages/dd-trace/src/openfeature/index.js +0 -2
  182. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  183. package/packages/dd-trace/src/openfeature/register.js +35 -0
  184. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  185. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  186. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  187. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  188. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  189. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  190. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  191. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  192. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  193. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  194. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  195. package/packages/dd-trace/src/plugins/util/test.js +65 -8
  196. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  197. package/packages/dd-trace/src/proxy.js +99 -10
  198. package/packages/dd-trace/src/span_processor.js +5 -0
  199. package/packages/dd-trace/src/standalone/index.js +0 -36
  200. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  201. package/packages/dd-trace/src/util.js +26 -0
  202. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  203. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { channel, addHook } = require('../helpers/instrument')
4
4
  const shimmer = require('../../../datadog-shimmer')
5
+ const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
5
6
  const types = require('./types')
6
7
 
7
8
  const startChannel = channel('apm:grpc:server:request:start')
@@ -76,6 +77,22 @@ function wrapRegister (register) {
76
77
  }
77
78
  }
78
79
 
80
+ function wrapSetupHandlers (setupHandlers) {
81
+ return function (http2Server) {
82
+ // gRPC builds its transport on a core Node `Http2Server` and drives every
83
+ // call through its own span lifecycle over the raw 'stream' event. Mark the
84
+ // server so the http2 server instrumentation leaves it untraced; otherwise
85
+ // its re-enabled 'stream' path would wrap every gRPC call in a second
86
+ // web.request span and become the top frame. `_setupHandlers` is the single
87
+ // server-creation funnel across all supported versions (the older
88
+ // `createHttp2Server` does not exist), and it runs at bind time, before any
89
+ // request reaches the wrapped `emit`.
90
+ if (http2Server) http2Server[FOREIGN_HTTP2_SERVER] = true
91
+
92
+ return setupHandlers.apply(this, arguments)
93
+ }
94
+ }
95
+
79
96
  function createWrapEmit (call, ctx, onCancel) {
80
97
  return function wrapEmit (emit) {
81
98
  return function (event, arg1) {
@@ -149,6 +166,7 @@ function isEmitter (obj) {
149
166
 
150
167
  addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3'], file: 'build/src/server.js' }, server => {
151
168
  shimmer.wrap(server.Server.prototype, 'register', wrapRegister)
169
+ shimmer.wrap(server.Server.prototype, '_setupHandlers', wrapSetupHandlers)
152
170
 
153
171
  return server
154
172
  })
@@ -49,10 +49,19 @@ function Hook (modules, hookOptions, onrequire) {
49
49
  this._patched = Object.create(null)
50
50
  const patched = new WeakMap()
51
51
 
52
+ /**
53
+ * @param {object|Function|undefined} moduleExports
54
+ * @param {string} moduleName
55
+ * @param {string|undefined} moduleBaseDir
56
+ * @param {string|undefined} moduleVersion
57
+ * @param {boolean|undefined} isIitm
58
+ */
52
59
  const safeHook = (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
53
60
  const parts = [moduleBaseDir, moduleName].filter(Boolean)
54
61
  const filename = path.join(...parts)
55
62
 
63
+ const defaultExport = isIitm && moduleExports.default
64
+ let defaultExportAliases
56
65
  let defaultWrapResult
57
66
 
58
67
  const wrappedOnrequire = (moduleExports, ...args) => {
@@ -77,17 +86,29 @@ function Hook (modules, hookOptions, onrequire) {
77
86
  }
78
87
 
79
88
  if (
80
- isIitm &&
81
- moduleExports.default &&
82
- (typeof moduleExports.default === 'object' ||
83
- typeof moduleExports.default === 'function')
89
+ defaultExport &&
90
+ (typeof defaultExport === 'object' ||
91
+ typeof defaultExport === 'function')
84
92
  ) {
85
- defaultWrapResult = wrappedOnrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm)
93
+ defaultWrapResult = wrappedOnrequire(defaultExport, moduleName, moduleBaseDir, moduleVersion, isIitm)
94
+ if (defaultWrapResult && defaultWrapResult !== defaultExport) {
95
+ defaultExportAliases = []
96
+ for (const exportName of Object.keys(moduleExports)) {
97
+ if (exportName !== 'default' && moduleExports[exportName] === defaultExport) {
98
+ defaultExportAliases.push(exportName)
99
+ }
100
+ }
101
+ }
86
102
  }
87
103
 
88
104
  const newExports = wrappedOnrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
89
105
 
90
- if (defaultWrapResult) newExports.default = defaultWrapResult
106
+ if (defaultWrapResult && defaultExportAliases) {
107
+ newExports.default = defaultWrapResult
108
+ for (const exportName of defaultExportAliases) {
109
+ moduleExports[exportName] = defaultWrapResult
110
+ }
111
+ }
91
112
 
92
113
  this._patched[filename] = true
93
114
 
@@ -4,19 +4,19 @@ const path = require('node:path')
4
4
 
5
5
  // Build-time half of the optional-peer mechanism shared by the webpack and esbuild plugins.
6
6
  //
7
- // Runtime files load an optional peer through `requireOptionalPeer('name')` (see
8
- // `require-optional-peer.js`), which bundlers cannot follow, so a build that does not opt into
9
- // the feature never pulls in the peer's (possibly optional) dependency chain (#8635). When the
10
- // peer is installed at build time the user has opted in, so the plugins rewrite the call into a
11
- // literal `require('name')` and let the bundler inline the peer, which keeps it working after
12
- // the bundle is relocated to a tree without the peer on disk (#8980). Peers that are absent at
13
- // build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
7
+ // Runtime files load an optional peer through a local `requireOptionalPeer('name')` wrapper.
8
+ // File tracers recognize its bound-require shape, while bundlers cannot follow the dynamic
9
+ // argument, so a build that does not opt into the feature never pulls in the peer's dependency
10
+ // chain (#8635). When the peer is installed at build time the user has opted in, so the plugins
11
+ // rewrite the call into a literal `require('name')` and let the bundler inline the peer, which
12
+ // keeps it working after the bundle is relocated without the peer on disk (#8980). Peers that
13
+ // are absent at build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
14
14
 
15
15
  // Files that load an optional peer this way, as suffixes of the resolved module path. The same
16
16
  // suffix matches the repo layout and `node_modules/dd-trace`. Add a file here to extend the
17
17
  // mechanism to a new optional peer; no plugin change is needed.
18
18
  const OPTIONAL_PEER_FILES = [
19
- 'packages/dd-trace/src/openfeature/flagging_provider.js',
19
+ 'packages/dd-trace/src/openfeature/require-provider.js',
20
20
  ]
21
21
 
22
22
  // Captures the peer name from `requireOptionalPeer('name')` / `requireOptionalPeer("name")`.
@@ -1,8 +1,34 @@
1
1
  'use strict'
2
2
 
3
- // Playwright 1.60 bundles several former hook targets into local classes/functions.
4
- // Keep these rewrites limited to private bundled internals that addHook cannot wrap.
3
+ // Playwright keeps several hook targets in private local classes/functions.
4
+ // Keep these rewrites limited to bundled internals that addHook cannot wrap.
5
5
  module.exports = [
6
+ {
7
+ module: {
8
+ name: 'playwright',
9
+ versionRange: '>=1.38.0 <1.51.0',
10
+ filePath: 'lib/index.js',
11
+ },
12
+ functionQuery: {
13
+ className: 'ArtifactsRecorder',
14
+ methodName: '_createScreenshotAttachmentPath',
15
+ kind: 'Sync',
16
+ },
17
+ channelName: 'ArtifactsRecorder_createScreenshotAttachmentPath',
18
+ },
19
+ {
20
+ module: {
21
+ name: 'playwright',
22
+ versionRange: '>=1.51.0',
23
+ filePath: 'lib/index.js',
24
+ },
25
+ functionQuery: {
26
+ className: 'SnapshotRecorder',
27
+ methodName: '_createAttachmentPath',
28
+ kind: 'Sync',
29
+ },
30
+ channelName: 'SnapshotRecorder_createAttachmentPath',
31
+ },
6
32
  {
7
33
  module: {
8
34
  name: 'playwright',
@@ -4,7 +4,7 @@ const shimmer = require('../../../datadog-shimmer')
4
4
  const { channel } = require('./instrument')
5
5
 
6
6
  const routerMountPaths = new WeakMap() // to track mount paths for router instances
7
- const layerMatchers = new WeakMap() // to store layer matchers
7
+ const layerMeta = new WeakMap() // per-layer middleware dispatch metadata (resolved name + route matchers)
8
8
  const appMountedRouters = new WeakSet() // to track routers mounted via app.use()
9
9
 
10
10
  const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
@@ -70,7 +70,7 @@ function collectRoutesFromRouter (router, prefix) {
70
70
  // Extract mount path from layer
71
71
  const mountPath = typeof layer.path === 'string'
72
72
  ? layer.path
73
- : getLayerMatchers(layer)?.[0]?.path || ''
73
+ : getLayerMeta(layer)?.matchers?.[0]?.path || ''
74
74
 
75
75
  const nestedPrefix = joinPath(prefix, mountPath)
76
76
  if (nestedPrefix === null) continue
@@ -121,12 +121,12 @@ function getRouterMountPaths (router) {
121
121
  return [...paths]
122
122
  }
123
123
 
124
- function setLayerMatchers (layer, matchers) {
125
- layerMatchers.set(layer, matchers)
124
+ function setLayerMeta (layer, meta) {
125
+ layerMeta.set(layer, meta)
126
126
  }
127
127
 
128
- function getLayerMatchers (layer) {
129
- return layerMatchers.get(layer)
128
+ function getLayerMeta (layer) {
129
+ return layerMeta.get(layer)
130
130
  }
131
131
 
132
132
  function normalizeMethodName (method) {
@@ -224,8 +224,8 @@ module.exports = {
224
224
  setRouterMountPath,
225
225
  getRouterMountPaths,
226
226
  joinPath,
227
- setLayerMatchers,
228
- getLayerMatchers,
227
+ setLayerMeta,
228
+ getLayerMeta,
229
229
  markAppMounted,
230
230
  isAppMounted,
231
231
  normalizeRoutePath,
@@ -1,18 +1,26 @@
1
1
  'use strict'
2
2
 
3
- // Old instrumentation temporarily replaced with compatibility mode only instrumentation.
4
- // See https://github.com/DataDog/dd-trace-js/issues/312
5
-
6
3
  const {
7
4
  channel,
8
5
  addHook,
9
6
  } = require('../helpers/instrument')
10
7
  const shimmer = require('../../../datadog-shimmer')
8
+ const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
11
9
 
12
10
  const startServerCh = channel('apm:http2:server:request:start')
13
11
  const errorServerCh = channel('apm:http2:server:request:error')
12
+ const adoptServerCh = channel('apm:http2:server:request:adopt')
14
13
  const emitCh = channel('apm:http2:server:response:emit')
15
14
 
15
+ const HTTP2_HEADER_METHOD = ':method'
16
+ const HTTP2_HEADER_PATH = ':path'
17
+ const HTTP2_HEADER_STATUS = ':status'
18
+
19
+ // Streams whose server span was already created from the 'stream' event. The
20
+ // compatibility layer synthesizes 'request' from that same stream, so the
21
+ // 'request' branch consults this set to avoid creating a second span.
22
+ const tracedStreams = new WeakSet()
23
+
16
24
  addHook({ name: 'http2' }, http2 => {
17
25
  shimmer.wrap(http2, 'createSecureServer', wrapCreateServer)
18
26
  shimmer.wrap(http2, 'createServer', wrapCreateServer)
@@ -36,34 +44,152 @@ function wrapResponseEmit (originalEmit, ctx) {
36
44
  }
37
45
  }
38
46
 
47
+ function wrapStreamEmit (originalEmit, ctx) {
48
+ // Named `emit`/arity-1 mirrors the stream method so the per-stream wrap skips
49
+ // its name/length rewrite. `this` is the Http2Stream; the plugin finishes on
50
+ // 'close', the same finish signal as the compatibility response.
51
+ return function emit (eventName) {
52
+ ctx.eventName = eventName
53
+ return emitCh.runStores(ctx, originalEmit, this, ...arguments)
54
+ }
55
+ }
56
+
39
57
  function wrapEmit (originalEmit) {
40
58
  // Named `emit` mirrors the server method so the one-time wrap skips its name
41
59
  // rewrite; rest params keep the per-event forwarding allocation-free.
42
60
  return function emit (...args) {
43
- if (!startServerCh.hasSubscribers) {
61
+ // A server owned by another instrumentation (e.g. @grpc/grpc-js) drives its
62
+ // own span lifecycle over the raw 'stream' API, so tracing it here would add
63
+ // a spurious web.request span on top of that integration's span and steal
64
+ // the top frame. Skip it entirely; the mark is set at server creation, so
65
+ // this is one property read on servers we do trace.
66
+ if (!startServerCh.hasSubscribers || this[FOREIGN_HTTP2_SERVER]) {
44
67
  return Reflect.apply(originalEmit, this, args)
45
68
  }
46
69
 
47
70
  const eventName = args[0]
48
- if (eventName === 'request') {
71
+ if (eventName === 'stream') {
72
+ // The compatibility layer synthesizes 'request' from an internal 'stream'
73
+ // listener it registers exactly once when a 'request' listener is added,
74
+ // so `listenerCount('stream')` exceeds one only when the application also
75
+ // registered a raw-stream listener. Owning the span here for that case
76
+ // keeps it active while the application's stream listener runs; the
77
+ // synthesized 'request' that fires nested below then reuses it. A
78
+ // request-only server (no raw-stream listener) is left to the 'request'
79
+ // branch so the compatibility response keeps its richer req/res.
80
+ const requestListenerCount = this.listenerCount('request')
81
+ if (requestListenerCount === 0 || this.listenerCount('stream') > 1) {
82
+ const stream = args[1]
83
+ const headers = args[2]
84
+ const ctx = createStreamAdapter(stream, headers)
85
+ // Only a mixed server (a 'request' listener is present) synthesizes a
86
+ // real request off this stream and adopts the span later, so only then
87
+ // does the context need keying on the stream. A raw-stream-only server
88
+ // never adopts; leaving the flag unset keeps the stream->context write
89
+ // off its hot path.
90
+ ctx.adoptable = requestListenerCount !== 0
91
+ tracedStreams.add(stream)
92
+
93
+ return traceServerRequest(ctx, () => {
94
+ shimmer.wrap(stream, 'emit', emit => wrapStreamEmit(emit, ctx))
95
+ return Reflect.apply(originalEmit, this, args)
96
+ })
97
+ }
98
+ } else if (eventName === 'request') {
49
99
  const req = args[1]
50
100
  const res = args[2]
51
101
  res.req = req
52
102
 
53
- const ctx = { req, res }
54
- return startServerCh.runStores(ctx, () => {
55
- shimmer.wrap(res, 'emit', emit => wrapResponseEmit(emit, ctx))
56
-
57
- try {
103
+ // A mixed server (raw-stream + 'request' listeners) already created the
104
+ // span from the 'stream' event above; the stream's single 'close' is the
105
+ // sole finish source, so skip creating a second span. The synthesized
106
+ // request/response are the real objects a user's 'request' handler and
107
+ // the finish `hooks.request` expect, so hand them to the existing
108
+ // stream-backed context rather than leaving it on the throwaway adapter.
109
+ if (tracedStreams.has(req.stream)) {
110
+ adoptServerCh.publish({ req, res })
111
+ } else {
112
+ const ctx = { req, res }
113
+ return traceServerRequest(ctx, () => {
114
+ shimmer.wrap(res, 'emit', emit => wrapResponseEmit(emit, ctx))
58
115
  return Reflect.apply(originalEmit, this, args)
59
- } catch (error) {
60
- ctx.error = error
61
- errorServerCh.publish(ctx)
62
-
63
- throw error
64
- }
65
- })
116
+ })
117
+ }
66
118
  }
119
+
67
120
  return Reflect.apply(originalEmit, this, args)
68
121
  }
69
122
  }
123
+
124
+ // Enter the request context and run `emitEvent` (the original `emit`, wrapped to
125
+ // publish per-event for the matching response/stream), publishing any synchronous
126
+ // throw from a user handler before letting it propagate.
127
+ /**
128
+ * @param {StreamRequestContext | { req: object, res: object }} ctx
129
+ * @param {() => unknown} emitEvent
130
+ */
131
+ function traceServerRequest (ctx, emitEvent) {
132
+ return startServerCh.runStores(ctx, () => {
133
+ try {
134
+ return emitEvent()
135
+ } catch (error) {
136
+ ctx.error = error
137
+ errorServerCh.publish(ctx)
138
+ throw error
139
+ }
140
+ })
141
+ }
142
+
143
+ /**
144
+ * The minimal req/res pair the shared web lifecycle (`web.js`) keys on, built
145
+ * from a core-API `Http2Stream`. The fields below are exactly the surface
146
+ * `web.js` / `url.js` / `ip_extractor.js` read for a stream-backed request; a
147
+ * new read added there must be mirrored here or it resolves to `undefined` on
148
+ * the core path only.
149
+ *
150
+ * @typedef {object} StreamRequestContext
151
+ * @property {object} req
152
+ * @property {import('node:http2').ServerHttp2Stream} req.stream branch key in `web.js`/`url.js`
153
+ * @property {import('node:http2').IncomingHttpHeaders} req.headers raw pseudo-header map
154
+ * @property {string} [req.method]
155
+ * @property {string} [req.url]
156
+ * @property {import('node:net').Socket} [req.socket] peer address source (OTel)
157
+ * @property {object} res
158
+ * @property {object} res.req back-reference used by `wrapResponseEmit`/finish
159
+ * @property {number} res.statusCode read at finish from `stream.sentHeaders`
160
+ * @property {(name: string) => string | number | string[] | undefined} res.getHeader response-header tagging
161
+ */
162
+
163
+ // Present the core-API stream + pseudo-header map as the minimal req/res pair
164
+ // the shared web lifecycle (`web.js`) consumes. The response status and headers
165
+ // are getters because `stream.sentHeaders` is empty until `stream.respond()`
166
+ // runs in the user handler; a snapshot taken here would always be `undefined`.
167
+ /**
168
+ * @param {import('node:http2').ServerHttp2Stream} stream
169
+ * @param {import('node:http2').IncomingHttpHeaders} headers
170
+ * @returns {StreamRequestContext}
171
+ */
172
+ function createStreamAdapter (stream, headers) {
173
+ const req = {
174
+ stream,
175
+ headers,
176
+ method: headers[HTTP2_HEADER_METHOD],
177
+ url: headers[HTTP2_HEADER_PATH],
178
+ socket: stream.session?.socket,
179
+ }
180
+ const res = {
181
+ req,
182
+ get statusCode () {
183
+ // A stream aborted before `stream.respond()` has no `:status`. The
184
+ // compatibility `Http2ServerResponse.statusCode` defaults to 200 in that
185
+ // case, so match it rather than report `undefined` (which `validateStatus`
186
+ // would treat as an error and which drops the `http.status_code` tag).
187
+ return stream.sentHeaders?.[HTTP2_HEADER_STATUS] ?? 200
188
+ },
189
+ getHeader (name) {
190
+ return stream.sentHeaders?.[name]
191
+ },
192
+ }
193
+
194
+ return { req, res }
195
+ }
@@ -1858,13 +1858,39 @@ function resetSuiteSkippingRunState () {
1858
1858
  coverageBackfillFiles = undefined
1859
1859
  }
1860
1860
 
1861
+ function resetLibraryConfiguration () {
1862
+ knownTests = {}
1863
+ isCodeCoverageEnabled = false
1864
+ isCoverageReportUploadEnabled = false
1865
+ isItrEnabled = false
1866
+ isSuitesSkippingEnabled = false
1867
+ isEarlyFlakeDetectionEnabled = false
1868
+ earlyFlakeDetectionNumRetries = 0
1869
+ earlyFlakeDetectionSlowTestRetries = {}
1870
+ earlyFlakeDetectionFaultyThreshold = 30
1871
+ isEarlyFlakeDetectionFaulty = false
1872
+ isKnownTestsEnabled = false
1873
+ isTestManagementTestsEnabled = false
1874
+ testManagementTests = {}
1875
+ testManagementAttemptToFixRetries = 0
1876
+ isImpactedTestsEnabled = false
1877
+ modifiedFiles = {}
1878
+ repositoryRoot = undefined
1879
+ }
1880
+
1861
1881
  function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
1862
1882
  if (!isItrEnabled || !isSuitesSkippingEnabled) return originalTests
1863
1883
 
1864
- const suitePathRoot = getRepositoryRootFromTest(originalTests[0], rootDir)
1865
- const jestSuitesToRun = getJestSuitesToRun(skippableSuites, originalTests, suitePathRoot)
1884
+ const repositoryRoot = getRepositoryRootFromTest(originalTests[0], rootDir)
1885
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, originalTests, repositoryRoot, rootDir)
1866
1886
  hasFilteredSkippableSuites = true
1867
- log.debug('%d out of %d suites are going to run.', jestSuitesToRun.suitesToRun.length, originalTests.length)
1887
+ log.debug(
1888
+ 'Jest discovered %d suites; skipped %d using %d skippable candidates; %d suites will run.',
1889
+ originalTests.length,
1890
+ jestSuitesToRun.skippedSuites.length,
1891
+ skippableSuites.length,
1892
+ jestSuitesToRun.suitesToRun.length
1893
+ )
1868
1894
  hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
1869
1895
  hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
1870
1896
 
@@ -1877,7 +1903,7 @@ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
1877
1903
  ? getTestContexts(originalTests)
1878
1904
  : undefined
1879
1905
  coverageBackfillFiles = isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
1880
- ? getCoverageBackfillFiles(skippableSuitesCoverage, suitePathRoot, getTestSuitePath)
1906
+ ? getCoverageBackfillFiles(skippableSuitesCoverage, repositoryRoot, getTestSuitePath)
1881
1907
  : undefined
1882
1908
 
1883
1909
  itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
@@ -2205,11 +2231,13 @@ function getCliWrapper (isNewJestVersion) {
2205
2231
  resetSuiteSkippingRunState()
2206
2232
  hasFinishedTestSession = false
2207
2233
 
2234
+ let shouldResetLibraryConfiguration = true
2208
2235
  try {
2209
2236
  const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh, {
2210
2237
  frameworkVersion: jestVersion,
2211
2238
  })
2212
2239
  if (!err) {
2240
+ shouldResetLibraryConfiguration = false
2213
2241
  isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
2214
2242
  isCoverageReportUploadEnabled = libraryConfig.isCoverageReportUploadEnabled
2215
2243
  isItrEnabled = libraryConfig.isItrEnabled
@@ -2225,6 +2253,10 @@ function getCliWrapper (isNewJestVersion) {
2225
2253
  }
2226
2254
  } catch (err) {
2227
2255
  log.error('Jest library configuration error', err)
2256
+ } finally {
2257
+ if (shouldResetLibraryConfiguration) {
2258
+ resetLibraryConfiguration()
2259
+ }
2228
2260
  }
2229
2261
 
2230
2262
  const {
@@ -135,7 +135,7 @@ function wrapPoolMethod (createConnection) {
135
135
 
136
136
  function wrapPoolGetConnectionMethod (getConnection) {
137
137
  return function wrappedGetConnection (...args) {
138
- const cb = args[args.length - 1]
138
+ const cb = args.at(-1)
139
139
  if (typeof cb !== 'function') return getConnection.apply(this, args)
140
140
 
141
141
  const ctx = {}
@@ -553,7 +553,6 @@ addHook({
553
553
 
554
554
  const runner = run.apply(this, args)
555
555
 
556
- // eslint-disable-next-line unicorn/no-array-for-each
557
556
  this.files.forEach((path) => {
558
557
  const isUnskippable = isMarkedAsUnskippable({ path })
559
558
  if (isUnskippable) {
@@ -915,7 +914,6 @@ addHook({
915
914
  status = 'skip'
916
915
  } else {
917
916
  // has to check every test in the test file
918
- // eslint-disable-next-line unicorn/no-array-for-each
919
917
  suitesInTestFile.forEach(suite => {
920
918
  suite.eachTest(test => {
921
919
  if (test.state === 'failed' || test.timedOut) {
@@ -229,7 +229,6 @@ function getSuitesByTestFile (root) {
229
229
  suitesByTestFile[suite.file] = [suite]
230
230
  }
231
231
  }
232
- // eslint-disable-next-line unicorn/no-array-for-each
233
232
  suite.suites.forEach(suite => {
234
233
  getSuites(suite)
235
234
  })
@@ -920,7 +919,6 @@ function getOnPendingHandler () {
920
919
  function getRunTestsWrapper (runTests, config) {
921
920
  return function (suite) {
922
921
  if (config.isTestManagementTestsEnabled) {
923
- // eslint-disable-next-line unicorn/no-array-for-each
924
922
  suite.tests.forEach((test) => {
925
923
  const { isAttemptToFix, isDisabled, isQuarantined } = getTestProperties(test, config.testManagementTests)
926
924
  if (isAttemptToFix && !test.isPending()) {
@@ -944,7 +942,6 @@ function getRunTestsWrapper (runTests, config) {
944
942
  }
945
943
 
946
944
  if (config.isImpactedTestsEnabled) {
947
- // eslint-disable-next-line unicorn/no-array-for-each
948
945
  suite.tests.forEach((test) => {
949
946
  isModifiedCh.publish({
950
947
  modifiedFiles: config.modifiedFiles,
@@ -968,7 +965,6 @@ function getRunTestsWrapper (runTests, config) {
968
965
 
969
966
  if (config.isKnownTestsEnabled) {
970
967
  // by the time we reach `this.on('test')`, it is too late. We need to add retries here
971
- // eslint-disable-next-line unicorn/no-array-for-each
972
968
  suite.tests.forEach((test) => {
973
969
  if (!test.isPending() && isNewTest(test, config.knownTests)) {
974
970
  test._ddIsNew = true
@@ -20,11 +20,11 @@ const collectionMethodsWithFilter = [
20
20
  'replaceOne',
21
21
  ] // findOne is ignored because it calls to find
22
22
 
23
- const collectionMethodsWithTwoFilters = [
23
+ const collectionMethodsWithTwoFilters = new Set([
24
24
  'findOneAndUpdate',
25
25
  'updateMany',
26
26
  'updateOne',
27
- ]
27
+ ])
28
28
 
29
29
  const startCh = channel('datadog:mongodb:collection:filter:start')
30
30
 
@@ -36,7 +36,7 @@ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
36
36
  for (const methodName of [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters]) {
37
37
  if (!(methodName in mongodb.Collection.prototype)) continue
38
38
 
39
- const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
39
+ const useTwoArguments = collectionMethodsWithTwoFilters.has(methodName)
40
40
 
41
41
  shimmer.wrap(mongodb.Collection.prototype, methodName, method => {
42
42
  return function (...args) {
@@ -55,11 +55,11 @@ const collectionMethodsWithFilter = [
55
55
  'remove',
56
56
  ]
57
57
 
58
- const collectionMethodsWithTwoFilters = [
58
+ const collectionMethodsWithTwoFilters = new Set([
59
59
  'findOneAndUpdate',
60
60
  'updateMany',
61
61
  'updateOne',
62
- ]
62
+ ])
63
63
 
64
64
  addHook({
65
65
  name: 'mongoose',
@@ -67,7 +67,7 @@ addHook({
67
67
  file: 'lib/model.js',
68
68
  }, Model => {
69
69
  for (const methodName of [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters]) {
70
- const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
70
+ const useTwoArguments = collectionMethodsWithTwoFilters.has(methodName)
71
71
  if (!(methodName in Model)) continue
72
72
 
73
73
  shimmer.wrap(Model, methodName, method => {
@@ -20,14 +20,14 @@ const methods = [
20
20
  'where',
21
21
  ]
22
22
 
23
- const methodsOptionalArgs = ['findOneAndUpdate']
23
+ const methodsOptionalArgs = new Set(['findOneAndUpdate'])
24
24
 
25
25
  function getFilters (args, methodName) {
26
26
  const [arg0, arg1] = args
27
27
 
28
28
  const filters = arg0 !== null && typeof arg0 === 'object' ? [arg0] : []
29
29
 
30
- if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
30
+ if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.has(methodName)) {
31
31
  filters.push(arg1)
32
32
  }
33
33
 
@@ -84,7 +84,7 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
84
84
  const finish = () => finishPoolQueryCh.publish(ctx)
85
85
 
86
86
  return startPoolQueryCh.runStores(ctx, () => {
87
- const cb = args[args.length - 1]
87
+ const cb = args.at(-1)
88
88
  if (typeof cb === 'function') {
89
89
  args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
90
90
  return finishPoolQueryCh.runStores(ctx, cb, this, ...args)
@@ -1,6 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
+ const nomenclature = require('../../dd-trace/src/service-naming')
5
+ const spanEndingHook = require('../../dd-trace/src/opentelemetry/span-ending-hook')
6
+ const { RESOURCE_NAME } = require('../../../ext/tags')
4
7
  const { channel, addHook } = require('./helpers/instrument')
5
8
 
6
9
  const startChannel = channel('apm:next:request:start')
@@ -21,6 +24,27 @@ const NEXT_REQUEST_META = Symbol.for('NextInternalRequestMeta')
21
24
  const META_IS_MIDDLEWARE = 'middlewareInvoke'
22
25
  const encounteredMiddleware = new WeakSet()
23
26
 
27
+ // `next.span_type` value Next.js sets on its own OTel root request span; the whole detection surface.
28
+ const NEXT_BASE_SERVER_HANDLE_REQUEST = 'BaseServer.handleRequest'
29
+
30
+ // In OTel-bridge mode (`plugins: false` + `new tracer.TracerProvider().register()`) Next emits its
31
+ // own OTel spans and renames the root request span to `${method} ${route}` at finish, which the
32
+ // bridge routes into the DD operation name and leaves the resource as the bare method — the reverse
33
+ // of Datadog's contract. Correct it via the bridge's pre-finish hook. See span-ending-hook.js.
34
+ spanEndingHook.hook = (ddSpan) => {
35
+ const tags = ddSpan.context().getTags()
36
+ if (tags['next.span_type'] !== NEXT_BASE_SERVER_HANDLE_REQUEST) return
37
+
38
+ const method = tags['http.method']
39
+ const route = tags['next.route'] ?? tags['http.route']
40
+ // Next already wrote the RSC-aware `${method} ${route}` into `next.span_name`; prefer it so we
41
+ // mirror Next's own naming, and only construct the resource when it is absent.
42
+ const resource = tags['next.span_name'] ?? (route ? `${method} ${route}` : method)
43
+
44
+ ddSpan.setOperationName(nomenclature.opName('web', 'server', 'next'))
45
+ ddSpan.setTag(RESOURCE_NAME, resource)
46
+ }
47
+
24
48
  function wrapHandleRequest (handleRequest) {
25
49
  return function (req, res, pathname, query) {
26
50
  return instrument(req, res, () => handleRequest.apply(this, arguments))
@@ -84,8 +84,6 @@ addHook({
84
84
  onDone: resolve,
85
85
  })
86
86
  })
87
- }).catch(() => {
88
- // Ignore errors - report generation failed
89
87
  })
90
88
  }
91
89