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
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ // In PM2 cluster mode, per-app env vars arrive as a `pm2_env`
4
+ // JSON string after preloads have run, so extract them first.
5
+ var pm2EnvStr = process.env.pm2_env
6
+ if (typeof pm2EnvStr === 'string') {
7
+ try {
8
+ var pm2Config = JSON.parse(pm2EnvStr)
9
+ var pm2Keys = Object.keys(pm2Config)
10
+ for (var i = 0; i < pm2Keys.length; i++) {
11
+ var key = pm2Keys[i]
12
+ var value = pm2Config[key]
13
+ process.env[key] = String(value)
14
+ }
15
+ } catch (error) {}
16
+ }
@@ -2,21 +2,12 @@
2
2
 
3
3
  /* eslint-disable no-console */
4
4
 
5
+ var logLevels = require('../log/levels')
5
6
  var isTrue = require('./util').isTrue
6
7
 
7
8
  var DD_TRACE_DEBUG = process.env.DD_TRACE_DEBUG
8
9
  var DD_TRACE_LOG_LEVEL = process.env.DD_TRACE_LOG_LEVEL
9
10
 
10
- var logLevels = {
11
- trace: 20,
12
- debug: 20,
13
- info: 30,
14
- warn: 40,
15
- error: 50,
16
- critical: 50,
17
- off: 100
18
- }
19
-
20
11
  var logLevel = isTrue(DD_TRACE_DEBUG)
21
12
  ? Number(DD_TRACE_LOG_LEVEL) || logLevels.debug
22
13
  : logLevels.off
@@ -185,7 +185,7 @@ function toNumberString (buffer, radix) {
185
185
  let low = readInt32(buffer, buffer.length - 4)
186
186
  let str = ''
187
187
 
188
- radix = radix || 10
188
+ radix ||= 10
189
189
 
190
190
  while (1) {
191
191
  const mod = (high % radix) * UINT_MAX + low
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
- SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
4
+ SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval', 'experiment'],
5
5
  SPAN_KIND: '_ml_obs.meta.span.kind',
6
6
  SESSION_ID: '_ml_obs.session_id',
7
7
  SESSION_ID_TRACE_DEFAULT_KEY: '_ml_obs.trace_session_id',
@@ -0,0 +1,249 @@
1
+ 'use strict'
2
+
3
+ const tracerVersion = require('../../../../package.json').version
4
+
5
+ /**
6
+ * Kind of event submitted through the evaluation metrics intake. Both kinds share the
7
+ * writer, the endpoint and most of their validation, and are told apart on the wire by
8
+ * the `event_kind` field.
9
+ * @typedef {'evaluation' | 'feedback'} MetricEventKind
10
+ */
11
+
12
+ /**
13
+ * Tag value the intake accepts: a string, a nullish value, or anything the SDK can coerce
14
+ * through `toString()`.
15
+ * @typedef {string | null | undefined | { toString?: unknown }} MetricTagValue
16
+ */
17
+
18
+ /** @type {string[]} */
19
+ const EVALUATION_METRIC_TYPES = ['categorical', 'score', 'boolean', 'json']
20
+
21
+ /**
22
+ * End-user feedback additionally accepts free-form text values.
23
+ * @type {string[]}
24
+ */
25
+ const FEEDBACK_METRIC_TYPES = [...EVALUATION_METRIC_TYPES, 'text']
26
+
27
+ /** @type {Record<MetricEventKind, string[]>} */
28
+ const METRIC_TYPES = {
29
+ evaluation: EVALUATION_METRIC_TYPES,
30
+ feedback: FEEDBACK_METRIC_TYPES,
31
+ }
32
+
33
+ /**
34
+ * Feedback target option accepted by the SDK, mapped to the payload key it is sent as. `span` and
35
+ * `spanId` are two ways of naming the same target.
36
+ * @type {Record<string, string>}
37
+ */
38
+ const FEEDBACK_TARGET_KEYS = {
39
+ span: 'span_id',
40
+ spanId: 'span_id',
41
+ traceId: 'trace_id',
42
+ sessionId: 'session_id',
43
+ feedbackJoinKey: 'feedback_join_key',
44
+ }
45
+
46
+ // The validation is shared but the user-facing wording is per-kind. Keeping the terms in one
47
+ // table avoids threading three near-identical strings through every validator.
48
+ /** @type {Record<MetricEventKind, { metric: string, metrics: string, data: string }>} */
49
+ const TERMS = {
50
+ evaluation: {
51
+ metric: 'evaluation metric',
52
+ metrics: 'evaluation metrics',
53
+ data: 'Evaluation metric data',
54
+ },
55
+ feedback: {
56
+ metric: 'feedback metric',
57
+ metrics: 'feedback metrics',
58
+ data: 'Feedback data',
59
+ },
60
+ }
61
+
62
+ /**
63
+ * @param {string[]} metricTypes
64
+ * @returns {string} the quoted list, e.g. `"categorical", "score" or "json"`
65
+ */
66
+ function formatMetricTypes (metricTypes) {
67
+ const quoted = metricTypes.map(metricType => `"${metricType}"`)
68
+ return `${quoted.slice(0, -1).join(', ')} or ${quoted.at(-1)}`
69
+ }
70
+
71
+ // Built once at load time: only ever used to build an error message.
72
+ /** @type {Record<MetricEventKind, string>} */
73
+ const METRIC_TYPES_MESSAGE = {
74
+ evaluation: formatMetricTypes(EVALUATION_METRIC_TYPES),
75
+ feedback: formatMetricTypes(FEEDBACK_METRIC_TYPES),
76
+ }
77
+
78
+ /**
79
+ * Builds an error carrying the telemetry error tag the SDK reports for the failed submission.
80
+ * @param {string} errorTag - Telemetry `error_type` tag value.
81
+ * @param {string} message - User-facing message.
82
+ * @returns {Error} the tagged error, to be thrown by the caller.
83
+ */
84
+ function taggedError (errorTag, message) {
85
+ const error = new Error(message)
86
+ // Same shape as the tagger's failures, which the SDK already reads back as `e.ddErrorTag`.
87
+ Object.defineProperty(error, 'ddErrorTag', { get () { return errorTag } })
88
+ return error
89
+ }
90
+
91
+ /**
92
+ * @param {MetricEventKind} kind
93
+ * @returns {Error} the tagged error, to be thrown by the caller.
94
+ */
95
+ function invalidTagsError (kind) {
96
+ return taggedError('invalid_tags', `Failed to parse tags. Tags for ${TERMS[kind].metrics} must be strings`)
97
+ }
98
+
99
+ /**
100
+ * @param {unknown} timestampMs
101
+ * @param {MetricEventKind} kind
102
+ * @returns {void}
103
+ */
104
+ function validateTimestamp (timestampMs, kind) {
105
+ if (typeof timestampMs !== 'number' || timestampMs < 0) {
106
+ throw taggedError(
107
+ 'invalid_timestamp',
108
+ `timestampMs must be a non-negative integer. ${TERMS[kind].data} will not be sent`
109
+ )
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Validates the label and returns it as sent on the wire.
115
+ * @param {unknown} label
116
+ * @param {MetricEventKind} kind
117
+ * @returns {unknown} the label, coerced to a string for feedback and left untouched for evaluations.
118
+ */
119
+ function validateLabel (label, kind) {
120
+ if (!label) {
121
+ throw taggedError('invalid_metric_label', `label must be the specified name of the ${TERMS[kind].metric}`)
122
+ }
123
+
124
+ // Evaluations keep the looser check they shipped with: a dotted or non-string label reaches the
125
+ // intake as-is. Aligning them with feedback rejects calls that succeed today, so it is deferred.
126
+ if (kind === 'evaluation') return label
127
+
128
+ // A dot makes the label unusable as a facet key.
129
+ const labelValue = String(label)
130
+ if (labelValue.includes('.')) {
131
+ throw taggedError('invalid_label_value', 'label value must not contain a "."')
132
+ }
133
+
134
+ return labelValue
135
+ }
136
+
137
+ /**
138
+ * @param {string | undefined} metricType - Already lower-cased metric type.
139
+ * @param {MetricEventKind} kind
140
+ * @returns {void}
141
+ */
142
+ function validateMetricType (metricType, kind) {
143
+ if (!metricType || !METRIC_TYPES[kind].includes(metricType)) {
144
+ throw taggedError('invalid_metric_type', `metricType must be one of ${METRIC_TYPES_MESSAGE[kind]}`)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * @param {string | undefined} metricType - Metric type already validated for the submitted kind.
150
+ * @param {unknown} value
151
+ * @returns {void}
152
+ */
153
+ function validateMetricValue (metricType, value) {
154
+ if (metricType === 'categorical' && typeof value !== 'string') {
155
+ throw taggedError('invalid_metric_value', 'value must be a string for a categorical metric.')
156
+ }
157
+ if (metricType === 'score' && typeof value !== 'number') {
158
+ throw taggedError('invalid_metric_value', 'value must be a number for a score metric.')
159
+ }
160
+ if (metricType === 'boolean' && typeof value !== 'boolean') {
161
+ throw taggedError('invalid_metric_value', 'value must be a boolean for a boolean metric')
162
+ }
163
+ if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
164
+ throw taggedError('invalid_metric_value', 'value must be a JSON object for a json metric')
165
+ }
166
+ if (metricType === 'text' && typeof value !== 'string') {
167
+ throw taggedError('invalid_metric_value', 'value must be a string for a text metric')
168
+ }
169
+ }
170
+
171
+ /**
172
+ * @param {unknown} assessment
173
+ * @returns {void}
174
+ */
175
+ function validateAssessment (assessment) {
176
+ if (assessment != null && assessment !== 'pass' && assessment !== 'fail') {
177
+ throw taggedError('invalid_assessment', 'assessment must be pass or fail')
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @param {unknown} reasoning
183
+ * @returns {void}
184
+ */
185
+ function validateReasoning (reasoning) {
186
+ if (reasoning != null && typeof reasoning !== 'string') {
187
+ throw taggedError('invalid_reasoning', 'reasoning must be a string')
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Serializes the user-provided tags into the `key:value` list expected by the intake, along
193
+ * with the tags every submission carries.
194
+ * @param {Record<string, MetricTagValue> | undefined} tags - User-provided tags, coerced when not strings.
195
+ * @param {string} mlApp - Resolved ML app name.
196
+ * @param {MetricEventKind} kind
197
+ * @param {boolean} [otelEnabled] - Adds `source:otel` so the backend waits for OTel span conversion.
198
+ * @returns {string[]} the serialized tag list.
199
+ */
200
+ function buildMetricTags (tags, mlApp, kind, otelEnabled = false) {
201
+ // An array or a string would otherwise be walked by index and produce tags named after
202
+ // their offsets, which the intake would happily store.
203
+ if (tags != null && (typeof tags !== 'object' || Array.isArray(tags))) {
204
+ throw invalidTagsError(kind)
205
+ }
206
+
207
+ const metricTags = {
208
+ 'ddtrace.version': tracerVersion,
209
+ ml_app: mlApp,
210
+ }
211
+
212
+ if (tags) {
213
+ for (const key of Object.keys(tags)) {
214
+ const tag = tags[key]
215
+ if (typeof tag === 'string') {
216
+ metricTags[key] = tag
217
+ } else if (tag == null) {
218
+ // A nullish value can be intentional, so it is serialized rather than rejected. Checked
219
+ // before the `toString` lookup below, which would throw a raw TypeError on it.
220
+ metricTags[key] = String(tag)
221
+ } else if (typeof tag.toString === 'function') {
222
+ metricTags[key] = tag.toString()
223
+ } else {
224
+ // Every other value carries a `toString`, either its own or the one inherited from
225
+ // `Object.prototype`, so this is only reached for a null-prototype object.
226
+ throw invalidTagsError(kind)
227
+ }
228
+ }
229
+ }
230
+
231
+ if (otelEnabled) {
232
+ metricTags.source = 'otel'
233
+ }
234
+
235
+ return Object.entries(metricTags).map(([key, value]) => `${key}:${value}`)
236
+ }
237
+
238
+ module.exports = {
239
+ EVALUATION_METRIC_TYPES,
240
+ FEEDBACK_METRIC_TYPES,
241
+ FEEDBACK_TARGET_KEYS,
242
+ buildMetricTags,
243
+ validateAssessment,
244
+ validateLabel,
245
+ validateMetricType,
246
+ validateMetricValue,
247
+ validateReasoning,
248
+ validateTimestamp,
249
+ }
@@ -14,9 +14,11 @@ function apiHost (site) {
14
14
  }
15
15
 
16
16
  // Web-app host for dashboard URLs. Single-level sites (datadoghq.com,
17
- // ddog-gov.com) are served from the `app.` subdomain; regional sites
18
- // (us3.datadoghq.com, ap1.datadoghq.com) are used as-is.
17
+ // ddog-gov.com) are served from the `app.` subdomain; staging uses
18
+ // dd.datad0g.com; regional sites (us3.datadoghq.com, ap1.datadoghq.com)
19
+ // are used as-is.
19
20
  function appHost (site) {
21
+ if (site === 'datad0g.com') return 'dd.datad0g.com'
20
22
  return site.split('.').length === 2 ? `app.${site}` : site
21
23
  }
22
24
 
@@ -2,15 +2,35 @@
2
2
 
3
3
  const { API_BASE_PATH } = require('./client')
4
4
 
5
- // Immutable dataset record: { input, expectedOutput?, metadata? }.
5
+ // Dataset record: { input, expectedOutput?, metadata?, id? }.
6
+ // `id` may be user-provided before push or filled from the backend-created record.
6
7
  class DatasetRecord {
7
- constructor (input, expectedOutput = null, metadata = {}) {
8
+ constructor (input, expectedOutput = null, metadata = {}, id = null) {
8
9
  this.input = input
9
10
  this.expectedOutput = expectedOutput ?? null
10
11
  this.metadata = metadata ?? {}
12
+ this.id = id ?? null
11
13
  }
12
14
  }
13
15
 
16
+ function createdRecordsFromResponse (response) {
17
+ if (Array.isArray(response?.records)) return response.records
18
+ if (Array.isArray(response?.data)) return response.data
19
+ return []
20
+ }
21
+
22
+ function recordIdFromCreatedRecord (record) {
23
+ return String(record?.id ?? record?.attributes?.id ?? '')
24
+ }
25
+
26
+ function versionFromCreatedRecords (records) {
27
+ const versions = records
28
+ .map(record => Number(record?.attributes?.valid_from_version ?? record?.attributes?.version))
29
+ .filter(Number.isFinite)
30
+ if (versions.length === 0) return null
31
+ return Math.max(...versions)
32
+ }
33
+
14
34
  // A local buffer of dataset records, created remotely and pushed on first run
15
35
  // (or eagerly via push()). Pushes are incremental.
16
36
  class Dataset {
@@ -22,6 +42,8 @@ class Dataset {
22
42
  #id
23
43
  #projectId
24
44
  #pushedCount
45
+ #version
46
+ #latestVersion
25
47
 
26
48
  constructor (client, name, description = '') {
27
49
  this.#client = client
@@ -32,16 +54,20 @@ class Dataset {
32
54
  this.#id = null
33
55
  this.#projectId = null
34
56
  this.#pushedCount = 0
57
+ this.#version = null
58
+ this.#latestVersion = null
35
59
  }
36
60
 
37
61
  // Build a Dataset that already exists remotely (used by pullDataset).
38
- static fromExisting (client, name, description, id, projectId, records, recordIds) {
62
+ static fromExisting (client, name, description, id, projectId, records, recordIds, version, latestVersion) {
39
63
  const dataset = new Dataset(client, name, description)
40
64
  dataset.#id = id
41
65
  dataset.#projectId = projectId
42
66
  dataset.#records.push(...records)
43
67
  dataset.#recordIds.push(...recordIds)
44
68
  dataset.#pushedCount = records.length
69
+ dataset.#version = version ?? null
70
+ dataset.#latestVersion = latestVersion ?? version ?? null
45
71
  return dataset
46
72
  }
47
73
 
@@ -74,6 +100,14 @@ class Dataset {
74
100
  return this.#projectId
75
101
  }
76
102
 
103
+ version () {
104
+ return this.#version
105
+ }
106
+
107
+ latestVersion () {
108
+ return this.#latestVersion
109
+ }
110
+
77
111
  // Dashboard URL for this dataset, or null until pushed/pulled.
78
112
  url () {
79
113
  if (this.#id === null) return null
@@ -100,7 +134,12 @@ class Dataset {
100
134
  throw new Error(`Failed to create dataset '${this.#name}': ${err.message}`)
101
135
  }
102
136
  this.#id = response?.data?.id ?? null
137
+ if (this.#id === null) {
138
+ throw new Error(`Failed to create dataset '${this.#name}': backend response is missing dataset id`)
139
+ }
103
140
  this.#projectId = projectId
141
+ this.#version = response?.data?.attributes?.current_version ?? this.#version
142
+ this.#latestVersion = response?.data?.attributes?.current_version ?? this.#latestVersion
104
143
  }
105
144
 
106
145
  if (this.#pushedCount >= this.#records.length) return { pushedCount: 0, totalCount: 0 }
@@ -108,6 +147,9 @@ class Dataset {
108
147
  const pending = this.#records.slice(this.#pushedCount)
109
148
  const records = pending.map((rec) => {
110
149
  const out = { input: rec.input }
150
+ if (rec.id != null) {
151
+ out.id = rec.id
152
+ }
111
153
  if (rec.expectedOutput !== null && rec.expectedOutput !== undefined) {
112
154
  out.expected_output = rec.expectedOutput
113
155
  }
@@ -128,20 +170,30 @@ class Dataset {
128
170
  throw new Error(`Failed to push records to dataset '${this.#name}': ${err.message}`)
129
171
  }
130
172
 
131
- // The append-records response returns created records under a top-level
132
- // `records` field, not the usual `data` envelope.
133
- const created = response?.records
173
+ // The append-records response has used both a top-level `records` array
174
+ // and JSON:API `data` resources. Accept either so generated/custom record
175
+ // ids are preserved for experiment row tagging.
176
+ const created = createdRecordsFromResponse(response)
177
+ const pushedVersion = versionFromCreatedRecords(created)
178
+ if (pushedVersion === null) {
179
+ // The dataset contents changed, but the backend did not report the new
180
+ // version. Avoid pinning later experiments to the pre-append create version.
181
+ this.#version = null
182
+ } else {
183
+ this.#version = pushedVersion
184
+ this.#latestVersion = Math.max(Number(this.#latestVersion ?? pushedVersion), pushedVersion)
185
+ }
186
+
134
187
  let pushedCount = 0
135
- if (Array.isArray(created)) {
136
- for (const node of created) {
137
- const recordId = String(node?.id ?? '')
138
- if (recordId !== '') pushedCount++
139
- this.#recordIds.push(recordId)
188
+ for (const [index, node] of created.entries()) {
189
+ const recordId = recordIdFromCreatedRecord(node)
190
+ if (recordId !== '') {
191
+ pushedCount++
192
+ pending[index].id = recordId
140
193
  }
141
- for (let i = created.length; i < pending.length; i++) this.#recordIds.push('')
142
- } else {
143
- for (let i = 0; i < pending.length; i++) this.#recordIds.push('')
194
+ this.#recordIds.push(recordId)
144
195
  }
196
+ for (let i = created.length; i < pending.length; i++) this.#recordIds.push('')
145
197
 
146
198
  // Advance by the snapshotted pending count, not the live records length,
147
199
  // so records added while this push was in flight aren't skipped by the next push.