dd-trace 6.8.0 → 6.10.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 (209) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +140 -42
  44. package/index.d.ts +140 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +8 -5
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  55. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  56. package/packages/datadog-instrumentations/src/dns.js +21 -2
  57. package/packages/datadog-instrumentations/src/electron.js +1 -0
  58. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  59. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  60. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  61. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  62. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
  68. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  69. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  70. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  71. package/packages/datadog-instrumentations/src/jest.js +980 -234
  72. package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
  73. package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
  74. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  75. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  76. package/packages/datadog-instrumentations/src/mysql.js +46 -16
  77. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  78. package/packages/datadog-instrumentations/src/next.js +155 -23
  79. package/packages/datadog-instrumentations/src/openai.js +14 -0
  80. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  81. package/packages/datadog-instrumentations/src/pg.js +209 -11
  82. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  83. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  84. package/packages/datadog-instrumentations/src/promise.js +3 -3
  85. package/packages/datadog-instrumentations/src/router.js +195 -19
  86. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
  87. package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
  88. package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
  89. package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
  90. package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
  91. package/packages/datadog-instrumentations/src/when.js +3 -3
  92. package/packages/datadog-instrumentations/src/ws.js +5 -1
  93. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  94. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  95. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  97. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  98. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  99. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
  100. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  101. package/packages/datadog-plugin-electron/src/net.js +10 -4
  102. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  103. package/packages/datadog-plugin-http/src/client.js +1 -1
  104. package/packages/datadog-plugin-http2/src/client.js +1 -1
  105. package/packages/datadog-plugin-jest/src/index.js +6 -22
  106. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  107. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  108. package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
  109. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  110. package/packages/datadog-plugin-mocha/src/index.js +832 -4
  111. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  112. package/packages/datadog-plugin-next/src/index.js +51 -21
  113. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  114. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  115. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  116. package/packages/datadog-plugin-pg/src/index.js +66 -1
  117. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  118. package/packages/datadog-plugin-vitest/src/index.js +103 -27
  119. package/packages/datadog-plugin-ws/src/util.js +2 -1
  120. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  121. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  123. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  124. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  127. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  128. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  129. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  130. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  131. package/packages/dd-trace/src/appsec/index.js +48 -476
  132. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  133. package/packages/dd-trace/src/carrier.js +356 -0
  134. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  135. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  136. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  137. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  139. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  140. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  141. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  142. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  143. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  144. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  145. package/packages/dd-trace/src/config/helper.js +2 -0
  146. package/packages/dd-trace/src/config/index.js +16 -5
  147. package/packages/dd-trace/src/config/remote_config.js +1 -1
  148. package/packages/dd-trace/src/config/supported-configurations.json +18 -0
  149. package/packages/dd-trace/src/datastreams/context.js +6 -1
  150. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  151. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  152. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  153. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  154. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  155. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  156. package/packages/dd-trace/src/guardrails/log.js +1 -10
  157. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  158. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  159. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  160. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  161. package/packages/dd-trace/src/llmobs/index.js +34 -2
  162. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  163. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  165. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  166. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  167. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  168. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  169. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  170. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  171. package/packages/dd-trace/src/llmobs/util.js +78 -0
  172. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  173. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  174. package/packages/dd-trace/src/log/channels.js +1 -9
  175. package/packages/dd-trace/src/log/levels.js +12 -0
  176. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  177. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  179. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  180. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  181. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  182. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  183. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  184. package/packages/dd-trace/src/plugin_manager.js +2 -0
  185. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  186. package/packages/dd-trace/src/plugins/index.js +1 -0
  187. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  188. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  189. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  190. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  191. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
  192. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  193. package/packages/dd-trace/src/plugins/util/test.js +397 -74
  194. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  195. package/packages/dd-trace/src/plugins/util/web.js +76 -3
  196. package/packages/dd-trace/src/profiler.js +1 -1
  197. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  198. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  199. package/packages/dd-trace/src/proxy.js +1 -0
  200. package/packages/dd-trace/src/ritm.js +5 -0
  201. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  202. package/packages/dd-trace/src/sampling_rule.js +4 -2
  203. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  204. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  205. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  206. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  207. package/vendor/dist/pprof-format/index.js +1 -1
  208. package/version.js +10 -8
  209. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -2,6 +2,13 @@
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { channel, addHook } = require('./helpers/instrument')
5
+ const {
6
+ dispatchesAcquireSynchronously,
7
+ takePoolWaitTime,
8
+ wrapPoolClusterQueryMethod,
9
+ wrapPoolGetConnection,
10
+ wrapPoolQueryMethod,
11
+ } = require('./helpers/pool-acquire')
5
12
 
6
13
  addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connection => {
7
14
  const startCh = channel('apm:mysql:query:start')
@@ -17,6 +24,11 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
17
24
  const conf = this.config
18
25
  const ctx = { sql, conf }
19
26
 
27
+ const waitTime = takePoolWaitTime(this)
28
+ if (waitTime !== undefined) {
29
+ ctx.poolWaitTime = waitTime
30
+ }
31
+
20
32
  return startCh.runStores(ctx, () => {
21
33
  if (args[0].sql) {
22
34
  args[0].sql = ctx.sql
@@ -36,7 +48,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
36
48
  }
37
49
  ctx.result = result
38
50
 
39
- return finishCh.runStores(ctx, cb, this, error, result)
51
+ return finishCh.runStores(ctx, cb, this, ...arguments)
40
52
  })
41
53
  } else {
42
54
  res.once('end', () => finishCh.publish(ctx))
@@ -61,20 +73,24 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
61
73
  const connectionFinishCh = channel('apm:mysql:connection:finish')
62
74
  const startPoolQueryCh = channel('datadog:mysql:pool:query:start')
63
75
  const finishPoolQueryCh = channel('datadog:mysql:pool:query:finish')
64
-
65
- shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
66
- arguments[0] = function (...args) {
67
- return connectionFinishCh.runStores(ctx, cb, this, ...args)
68
- }
69
-
70
- const ctx = {}
71
-
72
- connectionStartCh.publish(ctx)
73
-
74
- return getConnection.apply(this, arguments)
75
- })
76
-
77
- shimmer.wrap(Pool.prototype, 'query', query => function (...args) {
76
+ const acquireStartCh = channel('apm:mysql:pool:acquire:start')
77
+ const acquireFinishCh = channel('apm:mysql:pool:acquire:finish')
78
+ const deferredPoolAcquire = !dispatchesAcquireSynchronously(
79
+ Pool.prototype.query,
80
+ Object.assign(Object.create(Pool.prototype), { config: { connectionConfig: {} } }),
81
+ 'getConnection',
82
+ ['SELECT 1', () => {}]
83
+ )
84
+
85
+ shimmer.wrap(Pool.prototype, 'getConnection', getConnection => wrapPoolGetConnection(getConnection, {
86
+ connectionStartCh,
87
+ connectionFinishCh,
88
+ acquireStartCh,
89
+ acquireFinishCh,
90
+ reentersQueuedCallbacks: true,
91
+ }, deferredPoolAcquire))
92
+
93
+ shimmer.wrap(Pool.prototype, 'query', query => wrapPoolQueryMethod(function (...args) {
78
94
  if (!startPoolQueryCh.hasSubscribers) {
79
95
  return query.apply(this, args)
80
96
  }
@@ -99,7 +115,21 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
99
115
 
100
116
  return retval
101
117
  })
102
- })
118
+ }, connectionStartCh, deferredPoolAcquire))
103
119
 
104
120
  return Pool
105
121
  })
122
+
123
+ // A `canRetry` failover re-invokes `query` with the same Query object. Key the acquire state by that
124
+ // object so a retry remains part of the query even if a future mysql version defers it.
125
+ addHook({ name: 'mysql', file: 'lib/PoolNamespace.js', versions: ['>=2'] }, PoolNamespace => {
126
+ const channels = {
127
+ connectionStartCh: channel('apm:mysql:connection:start'),
128
+ connectionFinishCh: channel('apm:mysql:connection:finish'),
129
+ acquireStartCh: channel('apm:mysql:pool:acquire:start'),
130
+ acquireFinishCh: channel('apm:mysql:pool:acquire:finish'),
131
+ }
132
+ shimmer.wrap(PoolNamespace.prototype, 'query', query => wrapPoolClusterQueryMethod(query, channels))
133
+
134
+ return PoolNamespace
135
+ })
@@ -5,10 +5,55 @@ const { errorMonitor } = require('node:events')
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
  const satisfies = require('../../../vendor/dist/semifies')
7
7
  const { channel, addHook } = require('./helpers/instrument')
8
+ const {
9
+ dispatchesAcquireSynchronously,
10
+ takePoolWaitTime,
11
+ wrapPoolClusterGetConnection,
12
+ wrapPoolClusterMethod,
13
+ wrapPoolGetConnection,
14
+ wrapPoolQueryMethod,
15
+ } = require('./helpers/pool-acquire')
8
16
 
9
17
  /** @type {WeakMap<object, Function>} */
10
18
  const wrappedOnResult = new WeakMap()
11
19
 
20
+ /** @type {WeakSet<object>} */
21
+ const flagWrappedNamespaces = new WeakSet()
22
+
23
+ const connectionStartCh = channel('apm:mysql2:connection:start')
24
+ const connectionFinishCh = channel('apm:mysql2:connection:finish')
25
+ const acquireStartCh = channel('apm:mysql2:pool:acquire:start')
26
+ const acquireFinishCh = channel('apm:mysql2:pool:acquire:finish')
27
+ const poolAcquireChannels = {
28
+ connectionStartCh,
29
+ connectionFinishCh,
30
+ acquireStartCh,
31
+ acquireFinishCh,
32
+ }
33
+
34
+ /**
35
+ * Bracket a pool-cluster namespace's internal acquires and key retry state by its stable callback.
36
+ * @param {{ query?: Function, execute?: Function, getConnection?: Function }} poolNamespace
37
+ */
38
+ function flagWrapNamespace (poolNamespace) {
39
+ if (poolNamespace == null || flagWrappedNamespaces.has(poolNamespace)) return
40
+ flagWrappedNamespaces.add(poolNamespace)
41
+
42
+ if (typeof poolNamespace.query === 'function') {
43
+ shimmer.wrap(poolNamespace, 'query', query => wrapPoolClusterMethod(query, connectionStartCh))
44
+ }
45
+ if (typeof poolNamespace.execute === 'function') {
46
+ shimmer.wrap(poolNamespace, 'execute', execute => wrapPoolClusterMethod(execute, connectionStartCh))
47
+ }
48
+ if (typeof poolNamespace.getConnection === 'function') {
49
+ shimmer.wrap(
50
+ poolNamespace,
51
+ 'getConnection',
52
+ getConnection => wrapPoolClusterGetConnection(getConnection, poolAcquireChannels)
53
+ )
54
+ }
55
+ }
56
+
12
57
  /**
13
58
  * @param {unknown} sql
14
59
  * @returns {string|undefined}
@@ -43,6 +88,11 @@ function wrapConnection (Connection, version) {
43
88
  const ctx = {}
44
89
 
45
90
  if (isQuery) {
91
+ const waitTime = takePoolWaitTime(this)
92
+ if (waitTime !== undefined) {
93
+ ctx.poolWaitTime = waitTime
94
+ }
95
+
46
96
  command.execute = wrapExecute(command, command.execute, ctx, this.config)
47
97
 
48
98
  return commandAddCh.runStores(ctx, addCommand, this, ...arguments)
@@ -212,22 +262,41 @@ function wrapConnection (Connection, version) {
212
262
  * by the queued query attach to the caller rather than the previous query that freed the connection.
213
263
  *
214
264
  * @param {Function} Pool
265
+ * @param {boolean} deferredPoolAcquire
215
266
  * @returns {Function}
216
267
  */
217
- function wrapGetConnection (Pool) {
218
- const connectionStartCh = channel('apm:mysql2:connection:start')
219
- const connectionFinishCh = channel('apm:mysql2:connection:finish')
268
+ function wrapGetConnection (Pool, deferredPoolAcquire) {
269
+ shimmer.wrap(
270
+ Pool.prototype,
271
+ 'getConnection',
272
+ getConnection => wrapPoolGetConnection(getConnection, poolAcquireChannels, deferredPoolAcquire)
273
+ )
220
274
 
221
- shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
222
- const ctx = {}
223
- arguments[0] = function (...args) {
224
- return connectionFinishCh.runStores(ctx, cb, this, ...args)
225
- }
226
-
227
- connectionStartCh.publish(ctx)
275
+ return Pool
276
+ }
228
277
 
229
- return getConnection.apply(this, arguments)
230
- })
278
+ /**
279
+ * mysql2 >=3.11.5 keeps the outer-query abort at the connection level, so the pool only needs the
280
+ * acquire wrap plus the pool-query flag around `query` / `execute`.
281
+ *
282
+ * @param {Function} Pool
283
+ * @returns {Function}
284
+ */
285
+ function wrapBasePool (Pool) {
286
+ const deferredQueryAcquire = defersPoolAcquire(Pool, 'query')
287
+ const deferredExecuteAcquire = defersPoolAcquire(Pool, 'execute')
288
+ wrapGetConnection(Pool, deferredQueryAcquire || deferredExecuteAcquire)
289
+
290
+ shimmer.wrap(
291
+ Pool.prototype,
292
+ 'query',
293
+ query => wrapPoolQueryMethod(query, connectionStartCh, deferredQueryAcquire)
294
+ )
295
+ shimmer.wrap(
296
+ Pool.prototype,
297
+ 'execute',
298
+ execute => wrapPoolQueryMethod(execute, connectionStartCh, deferredExecuteAcquire)
299
+ )
231
300
 
232
301
  return Pool
233
302
  }
@@ -240,10 +309,12 @@ function wrapGetConnection (Pool) {
240
309
  function wrapPool (Pool, version) {
241
310
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
242
311
  const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
312
+ const deferredQueryAcquire = defersPoolAcquire(Pool, 'query')
313
+ const deferredExecuteAcquire = defersPoolAcquire(Pool, 'execute')
243
314
 
244
- wrapGetConnection(Pool)
315
+ wrapGetConnection(Pool, deferredQueryAcquire || deferredExecuteAcquire)
245
316
 
246
- shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
317
+ shimmer.wrap(Pool.prototype, 'query', query => wrapPoolQueryMethod(function (sql, values, cb) {
247
318
  if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
248
319
 
249
320
  const resolvedSql = resolveSqlString(sql)
@@ -279,9 +350,9 @@ function wrapPool (Pool, version) {
279
350
  }
280
351
 
281
352
  return query.apply(this, arguments)
282
- })
353
+ }, connectionStartCh, deferredQueryAcquire))
283
354
 
284
- shimmer.wrap(Pool.prototype, 'execute', execute => function (sql, values, cb) {
355
+ shimmer.wrap(Pool.prototype, 'execute', execute => wrapPoolQueryMethod(function (sql, values, cb) {
285
356
  if (!startOuterQueryCh.hasSubscribers) return execute.apply(this, arguments)
286
357
 
287
358
  const resolvedSql = resolveSqlString(sql)
@@ -304,11 +375,25 @@ function wrapPool (Pool, version) {
304
375
  }
305
376
 
306
377
  return execute.apply(this, arguments)
307
- })
378
+ }, connectionStartCh, deferredExecuteAcquire))
308
379
 
309
380
  return Pool
310
381
  }
311
382
 
383
+ /**
384
+ * @param {Function} Pool
385
+ * @param {'query'|'execute'} method
386
+ * @returns {boolean}
387
+ */
388
+ function defersPoolAcquire (Pool, method) {
389
+ return !dispatchesAcquireSynchronously(
390
+ Pool.prototype[method],
391
+ Object.assign(Object.create(Pool.prototype), { config: { connectionConfig: {} } }),
392
+ 'getConnection',
393
+ ['SELECT 1', () => {}]
394
+ )
395
+ }
396
+
312
397
  /**
313
398
  * @param {Function} PoolCluster
314
399
  * @returns {Function}
@@ -319,6 +404,7 @@ function wrapPoolCluster (PoolCluster) {
319
404
 
320
405
  shimmer.wrap(PoolCluster.prototype, 'of', of => function (...args) {
321
406
  const poolNamespace = of.apply(this, args)
407
+ flagWrapNamespace(poolNamespace)
322
408
 
323
409
  if (startOuterQueryCh.hasSubscribers && !wrappedPoolNamespaces.has(poolNamespace)) {
324
410
  shimmer.wrap(poolNamespace, 'query', query => function (sql, values, cb) {
@@ -404,7 +490,7 @@ addHook(
404
490
  // mysql2 >=3.11.5 moved the pool onto BasePool in lib/base/pool.js.
405
491
  addHook(
406
492
  { name: 'mysql2', file: 'lib/base/pool.js', versions: ['>=3.11.5'] },
407
- /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapGetConnection)
493
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapBasePool)
408
494
  )
409
495
 
410
496
  // PoolNamespace.prototype.query does not exist in mysql2<2.3.0
@@ -412,3 +498,18 @@ addHook(
412
498
  { name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['2.3.0 - 3.11.4'] },
413
499
  /** @type {(moduleExports: unknown, version: string) => unknown} */ (wrapPoolCluster)
414
500
  )
501
+
502
+ // mysql2 >=3.11.5 keeps the outer-query abort at the connection level, so a cluster namespace only
503
+ // needs the pool-query flag so its internal query/execute acquire does not open an acquire span.
504
+ addHook(
505
+ { name: 'mysql2', file: 'lib/pool_cluster.js', versions: ['>=3.11.5'] },
506
+ /** @type {(moduleExports: unknown, version: string) => unknown} */ (PoolCluster => {
507
+ shimmer.wrap(PoolCluster.prototype, 'of', of => function () {
508
+ const poolNamespace = of.apply(this, arguments)
509
+ flagWrapNamespace(poolNamespace)
510
+ return poolNamespace
511
+ })
512
+
513
+ return PoolCluster
514
+ })
515
+ )
@@ -15,6 +15,7 @@ const queryParsedChannel = channel('apm:next:query-parsed')
15
15
 
16
16
  const requests = new WeakSet()
17
17
  const nodeNextRequestsToNextRequests = new WeakMap()
18
+ const requestErrors = new WeakMap()
18
19
 
19
20
  // Next.js <= 14.2.6
20
21
  const MIDDLEWARE_HEADER = 'x-middleware-invoke'
@@ -213,10 +214,8 @@ function wrapServeStatic (serveStatic) {
213
214
  }
214
215
 
215
216
  function finish (ctx, result, err) {
216
- if (err) {
217
- ctx.error = err
218
- errorChannel.publish(ctx)
219
- }
217
+ publishError(ctx.req, ctx, err)
218
+ requestErrors.delete(ctx.req)
220
219
 
221
220
  const maybeNextRequest = nodeNextRequestsToNextRequests.get(ctx.req)
222
221
  if (maybeNextRequest) {
@@ -226,6 +225,27 @@ function finish (ctx, result, err) {
226
225
  finishChannel.publish(ctx)
227
226
  }
228
227
 
228
+ function publishError (req, ctx, error) {
229
+ if (!error) return
230
+
231
+ req = req.originalRequest || req
232
+ let errors = requestErrors.get(req)
233
+ if (errors?.has(error)) return
234
+
235
+ if (!errors) {
236
+ errors = new Set()
237
+ requestErrors.set(req, errors)
238
+ }
239
+ errors.add(error)
240
+
241
+ if (ctx) {
242
+ ctx.error = error
243
+ errorChannel.publish(ctx)
244
+ } else {
245
+ errorChannel.publish({ error })
246
+ }
247
+ }
248
+
229
249
  // also wrapped in dist/server/future/route-handlers/app-route-route-handler.js
230
250
  // in versions below 13.3.0 that support middleware,
231
251
  // however, it is not provided as a class function or exported property
@@ -244,15 +264,11 @@ addHook({
244
264
  return NextRequestAdapter
245
265
  })
246
266
 
247
- // From next 15.4.1 each app-route build inlines its own copy of `fromNodeNextRequest`, so the hook
248
- // above no longer maps the node request to the app-route NextRequest and a thrown handler error
249
- // cannot reach `finish`. The app-route runtime reports real errors (redirect/notFound and other
250
- // control-flow signals excluded by next) through `RouteModule.onRequestError`, which next loads from
251
- // a precompiled `app-route*.runtime.{dev,prod}.js` bundle regardless of how the route chunk is
252
- // bundled. The bundler/experimental part of the name is matched with a pattern rather than
253
- // enumerated, so a variant next adds later is picked up without a code change.
254
- const patchedAppRouteModules = new WeakSet()
255
-
267
+ // From Next 15.4.1, route modules execute through precompiled runtime bundles that bypass the
268
+ // classic server hooks above. Match bundler and experimental filename variants without enumerating
269
+ // them so App Routes, Pages APIs, and App Pages reuse the existing Next request lifecycle.
270
+ const patchedRouteModules = new WeakSet()
271
+ const COMPILED_RUNTIME_PATH = 'dist/compiled/next-server/'
256
272
  function wrapOnRequestError (onRequestError) {
257
273
  return function (req, error) {
258
274
  if (error) {
@@ -262,21 +278,137 @@ function wrapOnRequestError (onRequestError) {
262
278
  }
263
279
  }
264
280
 
265
- function instrumentAppRouteRuntime (runtime) {
266
- const AppRouteRouteModule = runtime.AppRouteRouteModule
267
- const proto = AppRouteRouteModule?.prototype
268
- if (proto && typeof proto.onRequestError === 'function' && !patchedAppRouteModules.has(AppRouteRouteModule)) {
269
- patchedAppRouteModules.add(AppRouteRouteModule)
281
+ function getRoutePage (routeModule, fallbackPage) {
282
+ const definition = routeModule?.definition
283
+ if (typeof definition?.pathname === 'string') {
284
+ return { page: definition.pathname, isFilesystemPath: false }
285
+ }
286
+
287
+ if (typeof definition?.page === 'string') {
288
+ return { page: definition.page, isFilesystemPath: true }
289
+ }
290
+
291
+ return { page: fallbackPage, isFilesystemPath: false }
292
+ }
293
+
294
+ function publishRoutePage (ctx, routeModule, fallbackPage, isAppPath) {
295
+ if (!ctx || !pageLoadChannel.hasSubscribers) return
296
+
297
+ const pageData = getRoutePage(routeModule, fallbackPage)
298
+ if (pageData.page) {
299
+ pageLoadChannel.publish(isAppPath ? { ...pageData, isAppPath: true } : pageData)
300
+ }
301
+ }
302
+
303
+ function wrapAppRouteHandle (handle) {
304
+ return function (req, context) {
305
+ if (!startChannel.hasSubscribers && !queryParsedChannel.hasSubscribers) {
306
+ return handle.apply(this, arguments)
307
+ }
308
+
309
+ const res = { statusCode: 500 }
310
+ nodeNextRequestsToNextRequests.set(req, req)
311
+
312
+ return instrument(req, res, ctx => {
313
+ publishRoutePage(ctx, this, undefined, true)
314
+
315
+ return handle.apply(this, arguments).then(response => {
316
+ if (ctx) ctx.res.statusCode = response?.status || 200
317
+ return response
318
+ })
319
+ })
320
+ }
321
+ }
322
+
323
+ function instrumentRouteModule (RouteModule, method, wrapper, handleErrors) {
324
+ const proto = RouteModule?.prototype
325
+ if (!proto || patchedRouteModules.has(RouteModule)) return
326
+
327
+ patchedRouteModules.add(RouteModule)
328
+ if (typeof proto[method] === 'function') {
329
+ shimmer.wrap(proto, method, wrapper)
330
+ }
331
+ if (handleErrors && typeof proto.onRequestError === 'function') {
270
332
  shimmer.wrap(proto, 'onRequestError', wrapOnRequestError)
271
333
  }
334
+ }
335
+
336
+ function instrumentAppRouteRuntime (runtime) {
337
+ instrumentRouteModule(runtime.AppRouteRouteModule, 'handle', wrapAppRouteHandle, true)
272
338
  return runtime
273
339
  }
274
340
 
275
- addHook({
276
- name: 'next',
277
- versions: ['>=15.4.1'],
278
- filePattern: String.raw`dist/compiled/next-server/app-route[\w-]*\.runtime\.(?:dev|prod)\.js$`,
279
- }, instrumentAppRouteRuntime)
341
+ function wrapPagesApiRender (render) {
342
+ return function (req, res, context = {}) {
343
+ if (!startChannel.hasSubscribers && !queryParsedChannel.hasSubscribers) {
344
+ return render.apply(this, arguments)
345
+ }
346
+
347
+ return instrument(req, res, ctx => {
348
+ publishRoutePage(ctx, this, context.page, false)
349
+
350
+ const { onError } = context
351
+ return render.call(this, req, res, {
352
+ ...context,
353
+ onError: function (error) {
354
+ publishError(req, ctx, error)
355
+ return onError?.apply(this, arguments)
356
+ },
357
+ })
358
+ })
359
+ }
360
+ }
361
+
362
+ function instrumentPagesApiRuntime (runtime) {
363
+ const PagesAPIRouteModule = runtime.PagesAPIRouteModule || runtime.default
364
+ instrumentRouteModule(PagesAPIRouteModule, 'render', wrapPagesApiRender, false)
365
+ return runtime
366
+ }
367
+
368
+ function wrapAppPageRender (render) {
369
+ return function (req, res, context = {}) {
370
+ if (!startChannel.hasSubscribers && !queryParsedChannel.hasSubscribers) {
371
+ return render.apply(this, arguments)
372
+ }
373
+
374
+ return instrument(req, res, ctx => {
375
+ publishRoutePage(ctx, this, context.page, true)
376
+
377
+ return render.apply(this, arguments).then(result => {
378
+ const statusCode = result?.metadata?.statusCode
379
+ if (ctx && typeof statusCode === 'number') {
380
+ ctx.res.statusCode = statusCode
381
+ }
382
+
383
+ return result
384
+ }, error => {
385
+ if (ctx && (typeof ctx.res.statusCode !== 'number' || ctx.res.statusCode < 400)) {
386
+ ctx.res.statusCode = 500
387
+ }
388
+
389
+ throw error
390
+ })
391
+ })
392
+ }
393
+ }
394
+
395
+ function instrumentAppPageRuntime (runtime) {
396
+ const AppPageRouteModule = runtime.AppPageRouteModule || runtime.default
397
+ instrumentRouteModule(AppPageRouteModule, 'render', wrapAppPageRender, true)
398
+ return runtime
399
+ }
400
+
401
+ for (const [runtime, instrumentRuntime] of [
402
+ ['app-route', instrumentAppRouteRuntime],
403
+ ['pages-api', instrumentPagesApiRuntime],
404
+ ['app-page', instrumentAppPageRuntime],
405
+ ]) {
406
+ addHook({
407
+ name: 'next',
408
+ versions: ['>=15.4.1'],
409
+ filePattern: String.raw`${COMPILED_RUNTIME_PATH}${runtime}[\w-]*\.runtime\.(?:dev|prod)\.js$`,
410
+ }, instrumentRuntime)
411
+ }
280
412
 
281
413
  addHook({
282
414
  name: 'next',
@@ -210,10 +210,24 @@ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, expor
210
210
  return exports
211
211
  })
212
212
 
213
+ /**
214
+ * @typedef {{
215
+ * methodName: string,
216
+ * args: unknown[],
217
+ * basePath?: string,
218
+ * result?: Record<string, unknown>,
219
+ * error?: unknown,
220
+ * }} OpenAiContext
221
+ */
222
+
213
223
  /**
214
224
  * For streamed responses, we need to accumulate all of the content in
215
225
  * the chunks, and let the combined content be the final response.
216
226
  * This way, spans look the same as when not streamed.
227
+ *
228
+ * @param {{ headers: unknown, url: string }} response
229
+ * @param {{ method: string }} options
230
+ * @param {OpenAiContext} ctx
217
231
  */
218
232
  function wrapStreamIterator (response, options, ctx) {
219
233
  return function (itr) {
@@ -5,6 +5,12 @@ const { addHook } = require('./helpers/instrument')
5
5
  /** @type {((pattern: string | RegExp) => RegExp | undefined) | undefined} */
6
6
  let compileToRegexp
7
7
 
8
+ /** @type {((pattern: string) => { tokens: object[] } | undefined) | undefined} */
9
+ let parseTokens
10
+
11
+ /** @type {((route: string) => ((url: string) => object | undefined) | undefined) | undefined} */
12
+ let makeMatcher
13
+
8
14
  addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
9
15
  // 0.1.x and 6.x: `module.exports = (path, ...) => RegExp`.
10
16
  // 7.x: `module.exports = { pathToRegexp(path, ...) => RegExp }`.
@@ -29,6 +35,47 @@ addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
29
35
  return moduleExports
30
36
  })
31
37
 
38
+ // 8.x only: earlier majors also expose `parse`/`match`, but with incompatible shapes — 7.x returns
39
+ // `{ tokens }` too, yet its tokens are bare strings rather than typed nodes. Gating on the version
40
+ // keeps those from installing adapters the consumers would misread.
41
+ addHook({ name: 'path-to-regexp', versions: ['>=8'] }, moduleExports => {
42
+ const { parse, match } = moduleExports
43
+
44
+ if (typeof parse === 'function' && typeof match === 'function') {
45
+ parseTokens = pattern => {
46
+ let result
47
+ try {
48
+ result = parse(pattern)
49
+ } catch {
50
+ return
51
+ }
52
+ if (Array.isArray(result?.tokens)) return result
53
+ }
54
+
55
+ makeMatcher = route => {
56
+ let matcher
57
+ try {
58
+ // Callers read which params matched, never their values: skipping decode is faster and
59
+ // stops a malformed escape ('%ZZ') from throwing URIError, which would read as "no match".
60
+ matcher = match(route, { decode: false })
61
+ } catch {
62
+ return
63
+ }
64
+ return url => {
65
+ let result
66
+ try {
67
+ result = matcher(url)
68
+ } catch {
69
+ return
70
+ }
71
+ return result ? result.params : undefined
72
+ }
73
+ }
74
+ }
75
+
76
+ return moduleExports
77
+ })
78
+
32
79
  /**
33
80
  * Returns whatever path-to-regexp compile adapter the host most recently
34
81
  * loaded. Capture this once at addHook fire time so each express/router
@@ -41,4 +88,23 @@ function getCompileToRegexp () {
41
88
  return compileToRegexp
42
89
  }
43
90
 
44
- module.exports = { getCompileToRegexp }
91
+ /**
92
+ * Returns the host's path-to-regexp `parse` adapter (8.x token tree), or `undefined` when the
93
+ * host has not loaded an 8.x path-to-regexp (e.g. Express 4 ships 0.1.x, which has no `parse`).
94
+ * @returns {((pattern: string) => { tokens: object[] } | undefined) | undefined}
95
+ */
96
+ function getParse () {
97
+ return parseTokens
98
+ }
99
+
100
+ /**
101
+ * Returns the host's path-to-regexp 8.x `match` adapter: a factory `route => (url => params)` that
102
+ * compiles a route once and returns a matcher yielding the captured params (or undefined on no
103
+ * match / unusable route). `undefined` when the host has not loaded an 8.x path-to-regexp.
104
+ * @returns {((route: string) => ((url: string) => object | undefined) | undefined) | undefined}
105
+ */
106
+ function getMatch () {
107
+ return makeMatcher
108
+ }
109
+
110
+ module.exports = { getCompileToRegexp, getParse, getMatch }