dd-trace 6.12.0 → 6.14.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 (251) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -3,69 +3,106 @@
3
3
  const { LRUCache } = require('../../../vendor/dist/lru-cache')
4
4
 
5
5
  /**
6
- * @typedef {{ signature?: string, type?: string, name?: string }} RequestOperation
6
+ * @typedef {{ signature: string, type: import('graphql').OperationTypeNode, name?: string }} RequestOperation
7
7
  */
8
8
 
9
- const operationTypes = new Set(['query', 'mutation', 'subscription'])
10
-
11
- // Mercurius funnels every operation through `fastifyGraphQl`, but the parsed
12
- // document — and therefore the operation signature/type/name — is only known
13
- // once mercurius parses internally. The top-level request span opens before
14
- // that, and on the JIT warm path neither parse/validate nor execute fires, so
15
- // the span would otherwise be left with only the provisional resource. The cold
16
- // path caches the computed metadata; the request boundary reads it back on the
17
- // warm path. Bounded so a flood of distinct queries can't grow it without limit.
18
- //
19
- // The key is the operation name plus the raw query text, not the source alone:
20
- // mercurius keys its document LRU by source but compiles the JIT for a single
21
- // `operationName`, and the compiled query then serves that operation for every
22
- // later request that shares the source — regardless of the `operationName` those
23
- // requests ask for. A source-only key would hand a warm request for operation B
24
- // the metadata of whichever operation was cached last for that source (A),
25
- // mislabeling the span. Operation names cannot contain a newline, so it is a
26
- // safe separator that keeps the two parts from colliding.
27
- const requestOperationCache = new LRUCache({ max: 500 })
28
-
29
- // Mercurius also accepts a pre-parsed document AST as the source, which reaches
30
- // the request boundary as an object rather than query text — so there is no
31
- // string to key the LRU by. Mercurius keys its own document LRU by that source
32
- // object's identity, and the same object reaches the boundary on the warm path,
33
- // so a WeakMap keyed by the caller-owned document recovers the metadata without
34
- // mutating the document and releases with it. The value carries the requested
35
- // operation name so a JIT-only sibling selection is not handed another
36
- // operation's metadata (same reason the string cache keys by operation name).
37
- /** @type {WeakMap<object, Map<string | undefined, RequestOperation>>} */
38
- const documentOperationCache = new WeakMap()
9
+ const requestCacheMax = 500
39
10
 
40
11
  /**
41
- * @param {string} source - The raw query text; the same key mercurius uses.
42
- * @param {string | undefined} operationName - The requested operation name.
12
+ * @typedef {{
13
+ * document: WeakRef<import('graphql').DocumentNode>,
14
+ * operations: Map<string, RequestOperation>
15
+ * }} DocumentOperations
16
+ */
17
+
18
+ /**
19
+ * @typedef {{
20
+ * documentOperations: WeakMap<object, DocumentOperations>,
21
+ * documents: import('lru-cache')<string, WeakRef<import('graphql').DocumentNode>>,
22
+ * operations: import('lru-cache')<string, RequestOperation>
23
+ * }} RequestCache
24
+ */
25
+
26
+ /** @type {WeakMap<object, RequestCache>} */
27
+ const requestCaches = new WeakMap()
28
+
29
+ /**
30
+ * @param {object | undefined} owner
31
+ * @param {boolean | number | undefined} configuredLimit
32
+ * @returns {RequestCache | undefined}
33
+ */
34
+ function getRequestCache (owner, configuredLimit) {
35
+ if (!owner || configuredLimit === false || (typeof configuredLimit === 'number' && configuredLimit <= 0)) return
36
+
37
+ let requestCache = requestCaches.get(owner)
38
+ if (requestCache !== undefined) return requestCache
39
+
40
+ const max = typeof configuredLimit === 'number'
41
+ ? Math.min(Math.floor(configuredLimit), requestCacheMax)
42
+ : requestCacheMax
43
+ if (max <= 0) return
44
+
45
+ requestCache = {
46
+ documentOperations: new WeakMap(),
47
+ documents: new LRUCache({ max }),
48
+ operations: new LRUCache({ max }),
49
+ }
50
+ requestCaches.set(owner, requestCache)
51
+ return requestCache
52
+ }
53
+
54
+ /**
55
+ * @param {string | null | undefined} operationName
56
+ * @param {boolean} calculateSignature
43
57
  * @returns {string}
44
58
  */
45
- function requestOperationKey (source, operationName) {
46
- return `${operationName ?? ''}\n${source}`
59
+ function operationCacheKey (operationName, calculateSignature) {
60
+ return `${calculateSignature === false ? 0 : 1}\n${operationName ?? ''}`
47
61
  }
48
62
 
49
63
  /**
50
- * @param {unknown} source - Query text on the common path; a pre-parsed
51
- * document AST otherwise. Any other shape (mercurius rejects it before
52
- * execute) has no cache entry and yields undefined.
53
- * @param {string | undefined} operationName - The requested operation name.
64
+ * @param {unknown} source
65
+ * @param {string | null | undefined} operationName
66
+ * @param {boolean} calculateSignature
67
+ * @param {RequestCache | undefined} requestCache
54
68
  * @returns {RequestOperation | undefined}
55
69
  */
56
- function getCachedRequestOperation (source, operationName) {
70
+ function getCachedRequestOperation (source, operationName, calculateSignature, requestCache) {
71
+ if (requestCache === undefined) return
72
+
73
+ const operationKey = operationCacheKey(operationName, calculateSignature)
57
74
  if (typeof source === 'string') {
58
- return requestOperationCache.get(requestOperationKey(source, operationName))
75
+ // GraphQL operation names cannot contain newlines, so source text cannot collide with this prefix.
76
+ const key = `${operationKey}\n${source}`
77
+ let operation = requestCache.operations.get(key)
78
+ if (operation !== undefined) return operation
79
+
80
+ const document = requestCache.documents.get(source)?.deref()
81
+ if (document === undefined) return
82
+
83
+ operation = getRequestOperation(document, operationName, calculateSignature)
84
+ if (operation === undefined) return
85
+
86
+ requestCache.operations.set(key, operation)
87
+ return operation
59
88
  }
60
89
  if (source === null || typeof source !== 'object') return
61
- return documentOperationCache.get(source)?.get(operationName)
90
+
91
+ const cached = requestCache.documentOperations.get(source)
92
+ if (cached === undefined) return
93
+
94
+ let operation = cached.operations.get(operationKey)
95
+ const document = cached.document.deref()
96
+ if (operation !== undefined || document === undefined) return operation
97
+
98
+ operation = getRequestOperation(document, operationName, calculateSignature)
99
+ if (operation === undefined) return
100
+
101
+ cached.operations.set(operationKey, operation)
102
+ return operation
62
103
  }
63
104
 
64
105
  /**
65
- * A string source keys the text LRU; a document AST keys the WeakMap. Any other
66
- * shape has no usable key — mercurius rejects it before execute, so the warm
67
- * path never reaches the request span for it either.
68
- *
69
106
  * @param {unknown} source
70
107
  * @returns {source is string | object}
71
108
  */
@@ -74,104 +111,139 @@ function isCacheableSource (source) {
74
111
  }
75
112
 
76
113
  /**
77
- * Select the operation definition matching `operationName`, or the first one
78
- * when no name is given (graphql/mercurius default selection).
79
- *
80
114
  * @param {import('graphql').DocumentNode | undefined} document
81
- * @param {string | undefined} operationName
115
+ * @param {string | null | undefined} operationName
82
116
  * @returns {import('graphql').OperationDefinitionNode | undefined}
83
117
  */
84
118
  function getOperation (document, operationName) {
85
- /* istanbul ignore if: validate/execute only call this with a parsed GraphQL document. */
86
119
  if (!document || !Array.isArray(document.definitions)) return
87
120
 
121
+ let operation
88
122
  for (const definition of document.definitions) {
89
- if (operationTypes.has(definition?.operation) &&
90
- (!operationName || definition.name?.value === operationName)) {
91
- return definition
123
+ if (definition.kind !== 'OperationDefinition') continue
124
+
125
+ if (operationName != null) {
126
+ if (definition.name?.value === operationName) return definition
127
+ continue
92
128
  }
129
+
130
+ if (operation !== undefined) return
131
+ operation = definition
93
132
  }
133
+
134
+ return operation
94
135
  }
95
136
 
96
137
  /**
97
- * Refine the top-level graphql.request span (mercurius) from the parsed
98
- * document and cache the metadata so the JIT warm path — where no sub-span
99
- * fires — can recover the same tags at the request boundary.
100
- *
101
- * This runs at the first boundary that has the document (validate on the cold
102
- * path, which also precedes a pre-execute validation failure). It is idempotent
103
- * across the later execute boundary via the `ddRequestRefined` flag, and a
104
- * no-op for graphql-js/apollo/yoga, which never open a request span.
105
- *
106
- * Every named operation in the document is cached, not just the selected one:
107
- * a multi-operation document parses once, and a later request may select a
108
- * sibling operation that mercurius then serves exclusively through its JIT path
109
- * (no execute span), so its metadata has to be ready before that happens.
110
- *
111
138
  * @param {import('../../dd-trace/src/opentracing/span') | undefined} requestSpan
112
- * @param {import('graphql').DocumentNode | undefined} document
113
- * @param {unknown} requestSource - The raw source the request boundary saw:
114
- * query text on the common path, a pre-parsed document AST otherwise. The
115
- * cache is keyed by it, not by the parsed document, so the request boundary
116
- * recovers the metadata on the warm path from the same value mercurius keys
117
- * its own document LRU by. Any other shape has no usable key and is not
118
- * cached (the warm path never reaches this span for it either).
119
- * @param {string | undefined} operationName - The requested operation name.
120
- * @param {boolean} calculateSignature - The graphql plugin's `signature` config.
139
+ * @param {string} signature
140
+ * @param {import('graphql').OperationTypeNode | undefined} type
141
+ * @param {string | undefined} name
121
142
  */
122
- function refineRequestSpan (requestSpan, document, requestSource, operationName, calculateSignature) {
123
- /* istanbul ignore if: validate only refines after the request span and parsed document exist. */
124
- if (!requestSpan || requestSpan.ddRequestRefined || !document) return
143
+ function refineRequestSpanMetadata (requestSpan, signature, type, name) {
144
+ if (!requestSpan || requestSpan.ddRequestRefined) return
125
145
  requestSpan.ddRequestRefined = true
126
146
 
127
- const operation = getOperation(document, operationName)
128
- const type = operation?.operation
129
- const name = operation?.name?.value
130
- const signature = getSignature(document, name, type, calculateSignature)
131
-
132
147
  if (signature) requestSpan.setTag('resource.name', signature)
133
148
  if (type) requestSpan.setTag('graphql.operation.type', type)
134
149
  if (name) requestSpan.setTag('graphql.operation.name', name)
150
+ }
135
151
 
136
- if (!isCacheableSource(requestSource)) return
137
-
138
- // Cache the selected operation under the requested name (undefined selects
139
- // the document's first operation, so it shares the entry with that name).
140
- cacheRequestOperation(requestSource, operationName, { signature, type, name })
152
+ /**
153
+ * @param {{
154
+ * ddRequestRefined?: boolean,
155
+ * setTag: (key: string, value: string) => unknown
156
+ * } | undefined} requestSpan
157
+ * @param {import('graphql').DocumentNode | undefined} document
158
+ * @param {unknown} requestSource
159
+ * @param {string | null | undefined} operationName
160
+ * @param {boolean} calculateSignature
161
+ * @param {boolean} validated
162
+ * @param {RequestCache | undefined} requestCache
163
+ */
164
+ function refineRequestSpan (
165
+ requestSpan,
166
+ document,
167
+ requestSource,
168
+ operationName,
169
+ calculateSignature,
170
+ validated,
171
+ requestCache
172
+ ) {
173
+ /* istanbul ignore if: validate only refines after the request span and parsed document exist. */
174
+ if (!requestSpan || requestSpan.ddRequestRefined || !document) return
141
175
 
142
- // Cache every named operation so a JIT-only sibling selection is labeled from
143
- // this single parse instead of falling back to a bare operation name.
144
- for (const definition of document.definitions) {
145
- const definitionName = definition?.name?.value
146
- if (definitionName === undefined || !operationTypes.has(definition.operation)) continue
147
- if (definitionName === operationName) continue
148
-
149
- cacheRequestOperation(requestSource, definitionName, {
150
- signature: getSignature(document, definitionName, definition.operation, calculateSignature),
151
- type: definition.operation,
152
- name: definitionName,
153
- })
154
- }
176
+ const operation = getRequestOperation(document, operationName, calculateSignature)
177
+ if (operation === undefined) return
178
+
179
+ const { signature, type, name } = operation
180
+ refineRequestSpanMetadata(requestSpan, signature, type, name)
181
+
182
+ if (!validated || !requestCache || !isCacheableSource(requestSource)) return
183
+ cacheRequestOperation(
184
+ requestCache,
185
+ requestSource,
186
+ operationName,
187
+ calculateSignature,
188
+ operation,
189
+ document
190
+ )
155
191
  }
156
192
 
157
193
  /**
158
- * @param {string | import('graphql').DocumentNode} source - Query text keys the
159
- * text LRU; a caller-owned document AST keys the WeakMap (never mutated).
160
- * @param {string | undefined} operationName - The requested operation name.
194
+ * @param {RequestCache} requestCache
195
+ * @param {string | object} source
196
+ * @param {string | null | undefined} operationName
197
+ * @param {boolean} calculateSignature
161
198
  * @param {RequestOperation} operation
199
+ * @param {import('graphql').DocumentNode} document
162
200
  */
163
- function cacheRequestOperation (source, operationName, operation) {
201
+ function cacheRequestOperation (
202
+ requestCache,
203
+ source,
204
+ operationName,
205
+ calculateSignature,
206
+ operation,
207
+ document
208
+ ) {
209
+ const operationKey = operationCacheKey(operationName, calculateSignature)
210
+ const documentRef = new WeakRef(document)
164
211
  if (typeof source === 'string') {
165
- requestOperationCache.set(requestOperationKey(source, operationName), operation)
212
+ requestCache.operations.set(`${operationKey}\n${source}`, operation)
213
+ requestCache.documents.set(source, documentRef)
166
214
  return
167
215
  }
168
216
 
169
- let operations = documentOperationCache.get(source)
170
- if (operations === undefined) {
171
- operations = new Map()
172
- documentOperationCache.set(source, operations)
217
+ let cached = requestCache.documentOperations.get(source)
218
+ if (cached === undefined) {
219
+ cached = {
220
+ document: documentRef,
221
+ operations: new Map(),
222
+ }
223
+ requestCache.documentOperations.set(source, cached)
224
+ } else {
225
+ cached.document = documentRef
226
+ }
227
+ cached.operations.set(operationKey, operation)
228
+ }
229
+
230
+ /**
231
+ * @param {import('graphql').DocumentNode} document
232
+ * @param {string | null | undefined} operationName
233
+ * @param {boolean} calculateSignature
234
+ * @returns {RequestOperation | undefined}
235
+ */
236
+ function getRequestOperation (document, operationName, calculateSignature) {
237
+ const operation = getOperation(document, operationName)
238
+ if (operation === undefined) return
239
+
240
+ const type = operation.operation
241
+ const name = operation.name?.value
242
+ return {
243
+ signature: getSignature(document, name, type, calculateSignature),
244
+ type,
245
+ name,
173
246
  }
174
- operations.set(operationName, operation)
175
247
  }
176
248
 
177
249
  /**
@@ -268,8 +340,25 @@ function isApolloHealthCheck (operation) {
268
340
  selection.directives?.length === 0
269
341
  }
270
342
 
343
+ /**
344
+ * @param {import('graphql').GraphQLOutputType} type
345
+ * @returns {string}
346
+ */
347
+ function getBaseTypeName (type) {
348
+ let current = type
349
+ while ('ofType' in current) current = current.ofType
350
+ return current.name
351
+ }
352
+
271
353
  let tools
272
354
 
355
+ /**
356
+ * @param {import('graphql').DocumentNode} document
357
+ * @param {string | undefined} operationName
358
+ * @param {import('graphql').OperationTypeNode} operationType
359
+ * @param {boolean} [calculate]
360
+ * @returns {string}
361
+ */
273
362
  function getSignature (document, operationName, operationType, calculate) {
274
363
  if (calculate !== false && tools !== false) {
275
364
  try {
@@ -286,22 +375,21 @@ function getSignature (document, operationName, operationType, calculate) {
286
375
  }
287
376
  }
288
377
 
289
- if (operationType) {
290
- if (operationName) {
291
- return `${operationType} ${operationName}`
292
- }
293
- return operationType
378
+ if (operationName) {
379
+ return `${operationType} ${operationName}`
294
380
  }
295
-
296
- return operationName ?? ''
381
+ return operationType
297
382
  }
298
383
 
299
384
  module.exports = {
300
385
  extractErrorIntoSpanEvent,
386
+ getBaseTypeName,
301
387
  getCachedRequestOperation,
302
388
  getOperation,
389
+ getRequestCache,
303
390
  getSignature,
304
391
  isApolloHealthCheck,
305
392
  isApolloHealthCheckSource,
306
393
  refineRequestSpan,
394
+ refineRequestSpanMetadata,
307
395
  }
@@ -1,12 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { storage } = require('../../datadog-core')
4
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
4
  const GraphQLParsePlugin = require('./parse')
6
5
  const { extractErrorIntoSpanEvent, isApolloHealthCheck, refineRequestSpan } = require('./utils')
7
6
 
8
- const legacyStorage = storage('legacy')
9
-
10
7
  class GraphQLValidatePlugin extends TracingPlugin {
11
8
  static id = 'graphql'
12
9
  static operation = 'validate'
@@ -28,26 +25,6 @@ class GraphQLValidatePlugin extends TracingPlugin {
28
25
  const docSource = document ? GraphQLParsePlugin.documentSources.get(document) : undefined
29
26
  const source = this.config.source && document && docSource
30
27
 
31
- // Refine the top-level graphql.request span (mercurius) from the parsed
32
- // document. validate is the first boundary that has it and precedes both
33
- // execute and any pre-execute rejection (unknown field, GET mutation), so a
34
- // failing request still ends up with a resource and operation tags. The
35
- // request span, its operation name, and the raw source ride the active
36
- // store the request boundary entered (validate's own `ctx.currentStore` is
37
- // not populated yet). The cache is keyed by that raw source, not the parsed
38
- // document — for a pre-parsed AST mercurius validates a structuredClone, so
39
- // the document here is a different object from the one the boundary saw and
40
- // recovers on the warm path. No-op for graphql-js/apollo/yoga, which never
41
- // open a request span.
42
- const requestStore = legacyStorage.getStore()
43
- refineRequestSpan(
44
- requestStore?.graphqlRequestSpan,
45
- document,
46
- requestStore?.graphqlRequestSource,
47
- requestStore?.graphqlRequestOperationName,
48
- this.config.signature
49
- )
50
-
51
28
  this.startSpan('graphql.validate', {
52
29
  service: this.config.service,
53
30
  type: 'graphql',
@@ -68,6 +45,16 @@ class GraphQLValidatePlugin extends TracingPlugin {
68
45
  const errors = ctx.result
69
46
  const span = ctx?.currentStore?.span || this.activeSpan
70
47
 
48
+ refineRequestSpan(
49
+ ctx.currentStore?.graphqlRequestSpan,
50
+ document,
51
+ ctx.currentStore?.graphqlRequestSource,
52
+ ctx.currentStore?.graphqlRequestOperationName,
53
+ this.config.signature,
54
+ !errors?.length,
55
+ ctx.currentStore?.graphqlRequestCache
56
+ )
57
+
71
58
  if (errors?.length) {
72
59
  span.setTag('error', errors[0])
73
60
  for (const err of errors) {
@@ -8,8 +8,8 @@ const tags = require('../../../ext/tags')
8
8
  const formats = require('../../../ext/formats')
9
9
  const HTTP_HEADERS = formats.HTTP_HEADERS
10
10
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
11
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
11
12
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
12
- const log = require('../../dd-trace/src/log')
13
13
  const { stripQueryAndFragment } = require('../../dd-trace/src/util')
14
14
  const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
15
15
 
@@ -167,7 +167,7 @@ function addRequestHeaders (req, span, config) {
167
167
  }
168
168
 
169
169
  function normalizeClientConfig (config) {
170
- const validateStatus = getStatusValidator(config)
170
+ const validateStatus = getClientStatusValidator(config)
171
171
  const filter = getFilter(config)
172
172
  const propagationFilter = getFilter({ blocklist: config.propagationBlocklist })
173
173
  const headers = getHeaders(config)
@@ -183,19 +183,6 @@ function normalizeClientConfig (config) {
183
183
  }
184
184
  }
185
185
 
186
- function is400ErrorCode (code) {
187
- return code < 400 || code >= 500
188
- }
189
-
190
- function getStatusValidator (config) {
191
- if (typeof config.validateStatus === 'function') {
192
- return config.validateStatus
193
- } else if (config.hasOwnProperty('validateStatus')) {
194
- log.error('Expected `validateStatus` to be a function.')
195
- }
196
- return is400ErrorCode
197
- }
198
-
199
186
  function getFilter (config) {
200
187
  config = { ...config, blocklist: config.blocklist || [] }
201
188
 
@@ -4,12 +4,12 @@ const URL = require('url').URL
4
4
 
5
5
  const { storage } = require('../../datadog-core')
6
6
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
7
- const log = require('../../dd-trace/src/log')
8
7
  const tags = require('../../../ext/tags')
9
8
  const kinds = require('../../../ext/kinds')
10
9
  const formats = require('../../../ext/formats')
11
10
  const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
12
11
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
12
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
13
13
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
14
14
 
15
15
  const HTTP_HEADERS = formats.HTTP_HEADERS
@@ -165,21 +165,8 @@ function hasAmazonSignature (headers, path) {
165
165
  return false
166
166
  }
167
167
 
168
- function is400ErrorCode (code) {
169
- return code < 400 || code >= 500
170
- }
171
-
172
- function getStatusValidator (config) {
173
- if (typeof config.validateStatus === 'function') {
174
- return config.validateStatus
175
- } else if (config.hasOwnProperty('validateStatus')) {
176
- log.error('Expected `validateStatus` to be a function.')
177
- }
178
- return is400ErrorCode
179
- }
180
-
181
168
  function normalizeConfig (config) {
182
- const validateStatus = getStatusValidator(config)
169
+ const validateStatus = getClientStatusValidator(config)
183
170
  const filter = getFilter(config)
184
171
  const headers = getHeaders(config)
185
172
 
@@ -113,7 +113,6 @@ class JestPlugin extends CiPlugin {
113
113
  hasUnskippableSuites,
114
114
  hasForcedToRunSuites,
115
115
  error,
116
- isTestSessionFinalizationError,
117
116
  isEarlyFlakeDetectionEnabled,
118
117
  isEarlyFlakeDetectionFaulty,
119
118
  isTestManagementTestsEnabled,
@@ -124,9 +123,6 @@ class JestPlugin extends CiPlugin {
124
123
  this.testModuleSpan.setTag(TEST_STATUS, status)
125
124
 
126
125
  if (error) {
127
- if (isTestSessionFinalizationError) {
128
- this.tracer._exporter.setDeferredTestSuiteError?.(error)
129
- }
130
126
  this.testSessionSpan.setTag('error', error)
131
127
  this.testModuleSpan.setTag('error', error)
132
128
  }
@@ -163,7 +159,6 @@ class JestPlugin extends CiPlugin {
163
159
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
164
160
  }
165
161
 
166
- this.tracer._exporter.exportDeferredTestSuiteSpans?.()
167
162
  this.testModuleSpan.finish()
168
163
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
169
164
  this.testSessionSpan.finish()
@@ -334,7 +329,6 @@ class JestPlugin extends CiPlugin {
334
329
  this.pendingTestSuiteFinishes.add(pendingFinish)
335
330
 
336
331
  const finish = () => {
337
- this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
338
332
  testSuiteSpan.finish()
339
333
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
340
334
  // Suites potentially run in a different process than the session,
@@ -31,6 +31,13 @@ class BaseLangChainTracingPlugin extends TracingPlugin {
31
31
  }
32
32
  }
33
33
 
34
+ error (ctx) {
35
+ // NodeInterrupt or GraphInterrupt "errors" for control flow, do not mark as real errors
36
+ if (ctx.error?.is_bubble_up) return
37
+
38
+ super.error(ctx)
39
+ }
40
+
34
41
  bindStart (ctx) {
35
42
  // TODO(bengl): All this renaming is just so we don't have to change the existing handlers
36
43
  ctx.args = ctx.arguments