dd-trace 6.7.0 → 6.9.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 (272) 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 +14 -8
  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/playwright.js +5 -5
  20. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  21. package/ci/test-optimization-validation/generated-files.js +47 -37
  22. package/ci/test-optimization-validation/generated-test-contract.js +9 -7
  23. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  24. package/ci/test-optimization-validation/literal-glob.js +52 -0
  25. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  26. package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
  27. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  28. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  29. package/ci/test-optimization-validation/offline-output.js +6 -6
  30. package/ci/test-optimization-validation/package-check.js +94 -0
  31. package/ci/test-optimization-validation/plan-writer.js +183 -10
  32. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  33. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  34. package/ci/test-optimization-validation/redaction.js +18 -27
  35. package/ci/test-optimization-validation/report-writer.js +182 -48
  36. package/ci/test-optimization-validation/result-semantics.js +20 -3
  37. package/ci/test-optimization-validation/runner-command.js +41 -21
  38. package/ci/test-optimization-validation/runner-contract.js +79 -43
  39. package/ci/test-optimization-validation/safe-files.js +4 -4
  40. package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
  41. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  42. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  43. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  44. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  45. package/ci/test-optimization-validation/test-output.js +2 -1
  46. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  47. package/ci/vitest-no-worker-init-setup.mjs +377 -84
  48. package/index.d.ts +175 -14
  49. package/init.js +1 -17
  50. package/initialize.mjs +11 -0
  51. package/loader-hook.mjs +6 -4
  52. package/package.json +12 -4
  53. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  54. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  55. package/packages/datadog-esbuild/index.js +4 -2
  56. package/packages/datadog-esbuild/src/utils.js +17 -2
  57. package/packages/datadog-instrumentations/src/ai.js +61 -34
  58. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
  60. package/packages/datadog-instrumentations/src/cucumber.js +131 -51
  61. package/packages/datadog-instrumentations/src/dns.js +21 -2
  62. package/packages/datadog-instrumentations/src/electron.js +1 -0
  63. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  64. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  65. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  66. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  68. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  69. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  70. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  71. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  72. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
  73. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  75. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  76. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  77. package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
  78. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  79. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  80. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  81. package/packages/datadog-instrumentations/src/jest.js +986 -246
  82. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  83. package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
  84. package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
  85. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
  86. package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
  87. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  88. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  89. package/packages/datadog-instrumentations/src/next.js +155 -23
  90. package/packages/datadog-instrumentations/src/openai.js +14 -0
  91. package/packages/datadog-instrumentations/src/pg.js +180 -6
  92. package/packages/datadog-instrumentations/src/playwright.js +176 -81
  93. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  94. package/packages/datadog-instrumentations/src/promise.js +3 -3
  95. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  96. package/packages/datadog-instrumentations/src/router.js +195 -19
  97. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
  98. package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
  99. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  100. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  101. package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
  102. package/packages/datadog-instrumentations/src/when.js +3 -3
  103. package/packages/datadog-instrumentations/src/ws.js +5 -1
  104. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  105. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  106. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  107. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  108. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  109. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  110. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  111. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  112. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  113. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  114. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
  115. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  116. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  117. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  118. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  119. package/packages/datadog-plugin-electron/src/net.js +10 -4
  120. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  121. package/packages/datadog-plugin-fs/src/index.js +0 -4
  122. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  123. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  124. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  125. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  126. package/packages/datadog-plugin-http/src/client.js +3 -2
  127. package/packages/datadog-plugin-http2/src/client.js +1 -1
  128. package/packages/datadog-plugin-jest/src/index.js +3 -21
  129. package/packages/datadog-plugin-jest/src/util.js +4 -1
  130. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  131. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  132. package/packages/datadog-plugin-mocha/src/index.js +342 -6
  133. package/packages/datadog-plugin-next/src/index.js +57 -9
  134. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  135. package/packages/datadog-plugin-pg/src/index.js +65 -1
  136. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  137. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  138. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  139. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  140. package/packages/datadog-plugin-undici/src/index.js +2 -1
  141. package/packages/datadog-plugin-vitest/src/index.js +179 -18
  142. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  143. package/packages/datadog-webpack/index.js +2 -2
  144. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  145. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  146. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  147. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  148. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  149. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  150. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  151. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  152. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  153. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  154. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  155. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  156. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  157. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  158. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  159. package/packages/dd-trace/src/appsec/index.js +48 -476
  160. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  162. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  163. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  164. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  165. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
  166. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  169. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  170. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  171. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  172. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  173. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  174. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
  175. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  176. package/packages/dd-trace/src/config/defaults.js +14 -6
  177. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  178. package/packages/dd-trace/src/config/helper.js +2 -0
  179. package/packages/dd-trace/src/config/index.js +31 -5
  180. package/packages/dd-trace/src/config/parsers.js +19 -1
  181. package/packages/dd-trace/src/config/remote_config.js +1 -1
  182. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  183. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  184. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  185. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  186. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  187. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  188. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  189. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  190. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  191. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  192. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  193. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  194. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  195. package/packages/dd-trace/src/flare/index.js +2 -2
  196. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  197. package/packages/dd-trace/src/guardrails/log.js +1 -10
  198. package/packages/dd-trace/src/id.js +1 -1
  199. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  200. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  201. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  202. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  203. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  204. package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
  205. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  206. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  207. package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
  208. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  209. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  210. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  211. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  212. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  213. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  214. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  215. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  216. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  217. package/packages/dd-trace/src/llmobs/sdk.js +152 -78
  218. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  219. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  220. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  221. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  222. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  223. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  224. package/packages/dd-trace/src/log/channels.js +1 -9
  225. package/packages/dd-trace/src/log/levels.js +12 -0
  226. package/packages/dd-trace/src/noop/proxy.js +2 -2
  227. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  228. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  229. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  230. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  231. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  232. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  233. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  234. package/packages/dd-trace/src/opentracing/span.js +1 -1
  235. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  236. package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
  237. package/packages/dd-trace/src/plugins/index.js +2 -0
  238. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  239. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  240. package/packages/dd-trace/src/plugins/util/ci.js +13 -6
  241. package/packages/dd-trace/src/plugins/util/git.js +7 -5
  242. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
  243. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  244. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  245. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  246. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  247. package/packages/dd-trace/src/plugins/util/test.js +356 -86
  248. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  249. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  250. package/packages/dd-trace/src/plugins/util/web.js +24 -2
  251. package/packages/dd-trace/src/priority_sampler.js +6 -3
  252. package/packages/dd-trace/src/profiler.js +1 -1
  253. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  254. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  255. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  256. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  257. package/packages/dd-trace/src/proxy.js +1 -0
  258. package/packages/dd-trace/src/ritm.js +10 -20
  259. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  260. package/packages/dd-trace/src/sampling_rule.js +4 -2
  261. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  262. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  263. package/packages/dd-trace/src/startup-log.js +3 -0
  264. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  265. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  266. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  267. package/packages/dd-trace/src/util.js +16 -0
  268. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  269. package/vendor/dist/pprof-format/index.js +1 -1
  270. package/vendor/dist/source-map/index.js +1 -1
  271. package/version.js +10 -8
  272. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -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) {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { errorMonitor } = require('node:events')
4
+ const { performance } = require('node:perf_hooks')
4
5
 
5
6
  const shimmer = require('../../datadog-shimmer')
6
7
  const {
@@ -18,10 +19,22 @@ const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
18
19
  const poolConnectStartCh = channel('apm:pg:pool:connect:start')
19
20
  const poolConnectFinishCh = channel('apm:pg:pool:connect:finish')
20
21
 
22
+ const poolAcquireStartCh = channel('apm:pg:pool:acquire:start')
23
+ const poolAcquireFinishCh = channel('apm:pg:pool:acquire:finish')
24
+
21
25
  // Drivers like pg-promise reuse the same prepared-statement query object across executions; cache
22
26
  // the un-injected `text` so the wrap doesn't capture a previous DBM injection as the new original.
23
27
  const originalTextCache = new WeakMap()
24
28
 
29
+ // Pool.query dispatches its client query synchronously from the connect callback, including nested
30
+ // `_release` queue pulses, so the wait can cross that callback without retaining the client.
31
+ const poolWaitClients = []
32
+ const poolWaitTimes = []
33
+
34
+ // Pool.query reports its internal acquire on the query span; explicit connects get an acquire span.
35
+ // Pool.query calls connect synchronously, so a module flag reliably distinguishes the two.
36
+ let acquiringForPoolQuery = false
37
+
25
38
  addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/native/client.js' }, Client => {
26
39
  shimmer.wrap(Client.prototype, 'query', query => wrapQuery(query))
27
40
  return Client
@@ -36,18 +49,74 @@ addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
36
49
  // pg defers a busy pool's connect callback and runs it in the releasing query's async context;
37
50
  // capture the caller's context and restore it around the callback so spans attach to the caller.
38
51
  shimmer.wrap(pg.Pool.prototype, 'connect', connect => function (cb) {
39
- if (typeof cb !== 'function' || !poolConnectStartCh.hasSubscribers) {
52
+ // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
53
+ if (typeof cb !== 'function') {
54
+ if (!poolAcquireStartCh.hasSubscribers) {
55
+ return connect.apply(this, arguments)
56
+ }
57
+
58
+ const start = acquireStart(this)
59
+ const acquireCtx = { poolOptions: this.options }
60
+ poolAcquireStartCh.publish(acquireCtx)
61
+
62
+ return connectForAcquire(connect, this, arguments, acquireCtx, start).then(client => {
63
+ acquireCtx.params = client.connectionParameters
64
+ finishAcquire(acquireCtx, start)
65
+ return client
66
+ }, error => {
67
+ acquireCtx.error = error
68
+ finishAcquire(acquireCtx, start)
69
+ throw error
70
+ })
71
+ }
72
+
73
+ if (!poolConnectStartCh.hasSubscribers) {
40
74
  return connect.apply(this, arguments)
41
75
  }
42
76
 
43
77
  const ctx = {}
44
- arguments[0] = function (...args) {
45
- return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
78
+ const start = acquireStart(this)
79
+ let acquireCtx
80
+
81
+ if (acquiringForPoolQuery) {
82
+ arguments[0] = function (...args) {
83
+ const client = args[1]
84
+ if (client !== undefined) {
85
+ poolWaitClients.push(client)
86
+ poolWaitTimes.push(acquireWait(start))
87
+ }
88
+ try {
89
+ return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
90
+ } finally {
91
+ if (client !== undefined) {
92
+ poolWaitClients.pop()
93
+ poolWaitTimes.pop()
94
+ }
95
+ }
96
+ }
97
+ } else {
98
+ acquireCtx = { poolOptions: this.options }
99
+ poolAcquireStartCh.publish(acquireCtx)
100
+
101
+ arguments[0] = function (...args) {
102
+ const client = args[1]
103
+ acquireCtx.error = args[0]
104
+ // A failed acquire has no client, so keep the parameters `connectForAcquire` snapshotted.
105
+ if (client !== undefined) {
106
+ acquireCtx.params = client.connectionParameters
107
+ }
108
+ finishAcquire(acquireCtx, start)
109
+ return poolConnectFinishCh.runStores(ctx, cb, this, ...args)
110
+ }
46
111
  }
47
112
 
48
113
  poolConnectStartCh.publish(ctx)
49
114
 
50
- return connect.apply(this, arguments)
115
+ if (acquireCtx === undefined) {
116
+ return connect.apply(this, arguments)
117
+ }
118
+
119
+ return connectForAcquire(connect, this, arguments, acquireCtx, start)
51
120
  })
52
121
  shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
53
122
  return pg
@@ -83,6 +152,17 @@ function wrapQuery (query) {
83
152
  abortController,
84
153
  stream,
85
154
  }
155
+
156
+ if (poolWaitClients.length !== 0) {
157
+ for (let i = poolWaitClients.length - 1; i >= 0; i--) {
158
+ if (poolWaitClients[i] === this) {
159
+ ctx.poolWaitTime = poolWaitTimes[i]
160
+ poolWaitClients[i] = undefined
161
+ break
162
+ }
163
+ }
164
+ }
165
+
86
166
  const finish = (error, res) => {
87
167
  if (error) {
88
168
  ctx.error = error
@@ -185,10 +265,102 @@ function wrapQuery (query) {
185
265
  const finish = (ctx) => {
186
266
  finishPoolQueryCh.publish(ctx)
187
267
  }
268
+
269
+ /**
270
+ * @typedef {{ database?: string, user?: string, host?: string, port?: number }} PoolConnectionParameters
271
+ * @typedef {{ connectionParameters?: PoolConnectionParameters }} PoolClient
272
+ * @typedef {{
273
+ * options?: PoolConnectionParameters,
274
+ * idleCount: number,
275
+ * waitingCount: number,
276
+ * _clients?: PoolClient[]
277
+ * }} InstrumentedPool
278
+ * @typedef {{
279
+ * poolOptions?: PoolConnectionParameters,
280
+ * params?: PoolConnectionParameters,
281
+ * error?: unknown,
282
+ * poolWaitTime?: number
283
+ * }} AcquireContext
284
+ * @typedef {{ length: number, [index: number]: unknown } & Iterable<unknown>} ArgumentsLike
285
+ */
286
+
287
+ // pg drains its pending queue FIFO on the next tick, so an idle client is only ours when it
288
+ // outnumbers the requests already queued ahead of us. That handoff is not a wait on the pool.
289
+ /**
290
+ * @param {InstrumentedPool} pool
291
+ */
292
+ function acquireStart (pool) {
293
+ return pool.idleCount > pool.waitingCount ? undefined : performance.now()
294
+ }
295
+
296
+ /**
297
+ * @param {number | undefined} start
298
+ */
299
+ function acquireWait (start) {
300
+ return start === undefined ? 0 : performance.now() - start
301
+ }
302
+
303
+ /**
304
+ * @param {InstrumentedPool} pool
305
+ */
306
+ function latestPoolConnectionParameters (pool) {
307
+ // pg-pool removes a failed client before invoking the acquire callback, so snapshot it while
308
+ // connect is still pending.
309
+ const clients = pool._clients
310
+ return clients?.at(-1)?.connectionParameters
311
+ }
312
+
313
+ /**
314
+ * @param {Function} connect
315
+ * @param {InstrumentedPool} pool
316
+ * @param {ArgumentsLike} args
317
+ * @param {AcquireContext} acquireCtx
318
+ * @param {number | undefined} start
319
+ */
320
+ function connectForAcquire (connect, pool, args, acquireCtx, start) {
321
+ let result
322
+ try {
323
+ result = connect.apply(pool, args)
324
+ } catch (error) {
325
+ // pg throws synchronously when it cannot even build a client, from a malformed connection
326
+ // string or a caller-supplied `Client` constructor, so the span needs finishing here.
327
+ acquireCtx.error = error
328
+ finishAcquire(acquireCtx, start)
329
+ throw error
330
+ }
331
+
332
+ acquireCtx.params = latestPoolConnectionParameters(pool)
333
+ return result
334
+ }
335
+
336
+ /**
337
+ * @param {AcquireContext} ctx
338
+ * @param {number | undefined} start
339
+ */
340
+ function finishAcquire (ctx, start) {
341
+ ctx.poolWaitTime = acquireWait(start)
342
+ poolAcquireFinishCh.publish(ctx)
343
+ }
344
+
345
+ /**
346
+ * @param {Function} query
347
+ * @param {InstrumentedPool} pool
348
+ * @param {unknown[]} args
349
+ */
350
+ function acquireForPoolQuery (query, pool, args) {
351
+ acquiringForPoolQuery = true
352
+ try {
353
+ return query.apply(pool, args)
354
+ } finally {
355
+ acquiringForPoolQuery = false
356
+ }
357
+ }
188
358
  function wrapPoolQuery (query) {
189
359
  return function (...args) {
190
360
  if (!startPoolQueryCh.hasSubscribers) {
191
- return query.apply(this, args)
361
+ return poolConnectStartCh.hasSubscribers
362
+ ? acquireForPoolQuery(query, this, args)
363
+ : query.apply(this, args)
192
364
  }
193
365
 
194
366
  const pgQuery = args[0] !== null && typeof args[0] === 'object' ? args[0] : { text: args[0] }
@@ -218,7 +390,9 @@ function wrapPoolQuery (query) {
218
390
  })
219
391
  }
220
392
 
221
- const retval = query.apply(this, args)
393
+ const retval = poolConnectStartCh.hasSubscribers
394
+ ? acquireForPoolQuery(query, this, args)
395
+ : query.apply(this, args)
222
396
 
223
397
  if (retval?.then) {
224
398
  retval.then(() => {