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
@@ -3,7 +3,6 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
5
  const { isError } = require('../util')
6
- const tracerVersion = require('../../../../package.json').version
7
6
  const logger = require('../log')
8
7
  const { getValueFromEnvSources } = require('../config/helper')
9
8
  const Span = require('../opentracing/span')
@@ -13,6 +12,17 @@ const {
13
12
  INPUT_VALUE,
14
13
  TRACE_ID,
15
14
  } = require('./constants/tags')
15
+ const {
16
+ FEEDBACK_METRIC_TYPES,
17
+ FEEDBACK_TARGET_KEYS,
18
+ buildMetricTags,
19
+ validateAssessment,
20
+ validateLabel,
21
+ validateMetricType,
22
+ validateMetricValue,
23
+ validateReasoning,
24
+ validateTimestamp,
25
+ } = require('./eval-metric')
16
26
  const {
17
27
  getFunctionArguments,
18
28
  validateKind,
@@ -35,12 +45,6 @@ class LLMObs extends NoopLLMObs {
35
45
  */
36
46
  #hasUserSpanProcessor = false
37
47
 
38
- /**
39
- * Lazily-created experiments facade (see ./experiments).
40
- * @type {import('./experiments').Experiments | undefined}
41
- */
42
- #experiments
43
-
44
48
  /**
45
49
  * @param {import('../tracer')} tracer - Tracer instance
46
50
  * @param {import('./index')} llmobsModule - LLMObs module instance
@@ -66,8 +70,7 @@ class LLMObs extends NoopLLMObs {
66
70
  * a clear message on use.
67
71
  */
68
72
  get experiments () {
69
- this.#experiments ??= createExperiments(this._config)
70
- return this.#experiments
73
+ return createExperiments(this._config, this)
71
74
  }
72
75
 
73
76
  enable (options = {}) {
@@ -318,14 +321,14 @@ class LLMObs extends NoopLLMObs {
318
321
  }
319
322
  throw e
320
323
  } finally {
321
- if (autoinstrumented === false) {
324
+ if (!autoinstrumented) {
322
325
  telemetry.recordLLMObsAnnotate(span, err)
323
326
  }
324
327
  }
325
328
  }
326
329
 
327
330
  exportSpan (span) {
328
- span = span || this._active()
331
+ span ||= this._active()
329
332
  let err = ''
330
333
  try {
331
334
  if (!span) {
@@ -399,92 +402,35 @@ class LLMObs extends NoopLLMObs {
399
402
  }
400
403
 
401
404
  const timestampMs = options.timestampMs || Date.now()
402
- if (typeof timestampMs !== 'number' || timestampMs < 0) {
403
- err = 'invalid_timestamp'
404
- throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
405
- }
405
+ validateTimestamp(timestampMs, 'evaluation')
406
406
 
407
407
  const { label, value, tags, reasoning, assessment, metadata } = options
408
408
  const metricType = options.metricType?.toLowerCase()
409
- if (!label) {
410
- err = 'invalid_metric_label'
411
- throw new Error('label must be the specified name of the evaluation metric')
412
- }
413
- if (!metricType || !['categorical', 'score', 'boolean', 'json'].includes(metricType)) {
414
- err = 'invalid_metric_type'
415
- throw new Error('metricType must be one of "categorical", "score", "boolean" or "json"')
416
- }
417
- if (metricType === 'categorical' && typeof value !== 'string') {
418
- err = 'invalid_metric_value'
419
- throw new Error('value must be a string for a categorical metric.')
420
- }
421
- if (metricType === 'score' && typeof value !== 'number') {
422
- err = 'invalid_metric_value'
423
- throw new Error('value must be a number for a score metric.')
424
- }
425
- if (metricType === 'boolean' && typeof value !== 'boolean') {
426
- err = 'invalid_metric_value'
427
- throw new Error('value must be a boolean for a boolean metric')
428
- }
429
- if (metricType === 'json' && !(typeof value === 'object' && value != null && !Array.isArray(value))) {
430
- err = 'invalid_metric_value'
431
- throw new Error('value must be a JSON object for a json metric')
432
- }
433
- if (assessment != null && assessment !== 'pass' && assessment !== 'fail') {
434
- err = 'invalid_assessment'
435
- throw new Error('assessment must be pass or fail')
436
- }
437
- if (reasoning != null && typeof reasoning !== 'string') {
438
- err = 'invalid_reasoning'
439
- throw new Error('reasoning must be a string')
440
- }
409
+ const labelValue = validateLabel(label, 'evaluation')
410
+ validateMetricType(metricType, 'evaluation')
411
+ validateMetricValue(metricType, value)
412
+ validateAssessment(assessment)
413
+ validateReasoning(reasoning)
441
414
  if (metadata != null && (typeof metadata !== 'object' || Array.isArray(metadata))) {
442
415
  err = 'invalid_metadata'
443
416
  throw new Error('metadata must be a JSON object')
444
417
  }
445
418
 
446
- const evaluationTags = {
447
- 'ddtrace.version': tracerVersion,
448
- ml_app: mlApp,
449
- }
450
-
451
- if (tags) {
452
- for (const key in tags) {
453
- const tag = tags[key]
454
- if (typeof tag === 'string') {
455
- evaluationTags[key] = tag
456
- } else if (typeof tag.toString === 'function') {
457
- evaluationTags[key] = tag.toString()
458
- } else if (tag == null) {
459
- evaluationTags[key] = Object.prototype.toString.call(tag)
460
- } else {
461
- // should be a rare case
462
- // every object in JS has a toString, otherwise every primitive has its own toString
463
- // null and undefined are handled above
464
- err = 'invalid_tags'
465
- throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
466
- }
467
- }
468
- }
469
-
470
- // When OTel tracing is enabled, add source:otel tag to allow backend to wait for OTel span conversion
471
- if (this._config.DD_TRACE_OTEL_ENABLED) {
472
- evaluationTags.source = 'otel'
473
- }
474
-
475
419
  const payload = {
420
+ event_kind: 'evaluation',
476
421
  join_on: {
477
422
  span: {
478
423
  span_id: spanId,
479
424
  trace_id: traceId,
480
425
  },
481
426
  },
482
- label,
427
+ label: labelValue,
483
428
  metric_type: metricType,
484
429
  ml_app: mlApp,
485
430
  [`${metricType}_value`]: value,
486
431
  timestamp_ms: timestampMs,
487
- tags: Object.entries(evaluationTags).map(([key, value]) => `${key}:${value}`),
432
+ // When OTel tracing is enabled, `source:otel` lets the backend wait for OTel span conversion
433
+ tags: buildMetricTags(tags, mlApp, 'evaluation', this._config.DD_TRACE_OTEL_ENABLED),
488
434
  }
489
435
  if (reasoning != null) {
490
436
  payload.reasoning = reasoning
@@ -498,11 +444,139 @@ class LLMObs extends NoopLLMObs {
498
444
  const currentStore = storage.getStore()
499
445
  const routing = currentStore?.routingContext
500
446
  evalMetricAppendCh.publish({ payload, routing })
447
+ } catch (e) {
448
+ if (e.ddErrorTag) err = e.ddErrorTag
449
+ throw e
501
450
  } finally {
502
451
  telemetry.recordSubmitEvaluation(options, err)
503
452
  }
504
453
  }
505
454
 
455
+ /**
456
+ * Submits end-user feedback for a span, trace, session, or customer-defined entity.
457
+ *
458
+ * Exactly one target must be provided: `span` (as returned by `llmobs.exportSpan()`) or
459
+ * `spanId` to target a span, `traceId` a trace, `sessionId` a session, or `feedbackJoinKey`
460
+ * a customer-defined entity.
461
+ * `label`, `metricType`, `value` and `submitter` are required, and are validated at call time.
462
+ * @param {object} [options] - The feedback options.
463
+ * @param {string} [options.label] - The name of the feedback metric.
464
+ * @param {'categorical' | 'score' | 'boolean' | 'json' | 'text'} [options.metricType] - The value type.
465
+ * @param {string | number | boolean | Record<string, unknown>} [options.value] - The feedback value,
466
+ * matching `metricType`.
467
+ * @param {{ id: string, type?: string }} [options.submitter] - Who submitted the feedback.
468
+ * @param {{ traceId: string, spanId: string }} [options.span] - Span context to attach the feedback to.
469
+ * @param {string} [options.spanId] - ID of the span to attach the feedback to.
470
+ * @param {string} [options.traceId] - ID of the trace to attach the feedback to.
471
+ * @param {string} [options.sessionId] - ID of the session to attach the feedback to.
472
+ * @param {string} [options.feedbackJoinKey] - Customer-defined key to attach the feedback to.
473
+ * @param {Record<string, string>} [options.tags] - Tags to attach to the feedback.
474
+ * @param {string} [options.mlApp] - The ML app name. Defaults to the configured one.
475
+ * @param {number} [options.timestampMs] - When the feedback was generated. Defaults to now.
476
+ * @param {'pass' | 'fail'} [options.assessment] - Assessment of the feedback.
477
+ * @param {string} [options.reasoning] - Explanation of the feedback.
478
+ * @returns {void}
479
+ */
480
+ submitFeedback (options = {}) {
481
+ if (!this.enabled) return
482
+
483
+ let err = ''
484
+ let targetType = 'other'
485
+ let metricTypeTag = 'other'
486
+ try {
487
+ const { span, spanId, traceId, sessionId, feedbackJoinKey, submitter } = options
488
+
489
+ // Resolved before any validation so telemetry still reports the metric type of a
490
+ // submission that fails on an unrelated field.
491
+ const metricType = options.metricType?.toLowerCase()
492
+ if (FEEDBACK_METRIC_TYPES.includes(metricType)) metricTypeTag = metricType
493
+
494
+ // The intake keys feedback off a single top-level identifier, so more than one target
495
+ // would be ambiguous and none would leave the feedback unattached. `span` also carries a
496
+ // traceId, but passing it is wire-equivalent to passing its `spanId` directly.
497
+ let targetName, targetValue
498
+ let targetCount = 0
499
+ if (span != null) { targetName = 'span'; targetValue = span.spanId; targetCount++ }
500
+ if (spanId != null) { targetName = 'spanId'; targetValue = spanId; targetCount++ }
501
+ if (traceId != null) { targetName = 'traceId'; targetValue = traceId; targetCount++ }
502
+ if (sessionId != null) { targetName = 'sessionId'; targetValue = sessionId; targetCount++ }
503
+ if (feedbackJoinKey != null) { targetName = 'feedbackJoinKey'; targetValue = feedbackJoinKey; targetCount++ }
504
+
505
+ if (targetCount !== 1) {
506
+ err = 'invalid_target_count'
507
+ throw new Error(
508
+ 'Exactly one of `span`, `spanId`, `traceId`, `sessionId` or `feedbackJoinKey` ' +
509
+ 'must be specified to submit feedback.'
510
+ )
511
+ }
512
+
513
+ targetType = FEEDBACK_TARGET_KEYS[targetName]
514
+ if (typeof targetValue !== 'string' || !targetValue) {
515
+ if (targetName === 'span') {
516
+ err = 'invalid_span'
517
+ throw new TypeError(
518
+ '`span` must be an object containing a non-empty string spanId. ' +
519
+ '`llmobs.exportSpan()` can be used to generate this object from a given span.'
520
+ )
521
+ }
522
+ err = `invalid_${targetType}`
523
+ throw new TypeError(`\`${targetName}\` must be a non-empty string`)
524
+ }
525
+
526
+ if (typeof submitter?.id !== 'string' || !submitter.id) {
527
+ err = 'invalid_submitter'
528
+ throw new TypeError('submitter must be an object containing a non-empty string id')
529
+ }
530
+ if (submitter.type != null && typeof submitter.type !== 'string') {
531
+ err = 'invalid_submitter'
532
+ throw new TypeError('submitter.type must be a string')
533
+ }
534
+
535
+ const mlApp = options.mlApp || this._config.llmobs.mlApp
536
+ if (!mlApp) {
537
+ err = 'missing_ml_app'
538
+ throw new Error('ML App name is required for sending feedback. Feedback data will not be sent.')
539
+ }
540
+
541
+ const timestampMs = options.timestampMs || Date.now()
542
+ validateTimestamp(timestampMs, 'feedback')
543
+
544
+ const { label, value, tags, reasoning, assessment } = options
545
+ const labelValue = validateLabel(label, 'feedback')
546
+ validateMetricType(metricType, 'feedback')
547
+ validateMetricValue(metricType, value)
548
+ validateAssessment(assessment)
549
+ validateReasoning(reasoning)
550
+
551
+ const payload = {
552
+ event_kind: 'feedback',
553
+ [targetType]: targetValue,
554
+ label: labelValue,
555
+ metric_type: metricType,
556
+ ml_app: mlApp,
557
+ [`${metricType}_value`]: value,
558
+ timestamp_ms: timestampMs,
559
+ tags: buildMetricTags(tags, mlApp, 'feedback'),
560
+ submitter: submitter.type == null ? { id: submitter.id } : { id: submitter.id, type: submitter.type },
561
+ }
562
+ if (reasoning != null) {
563
+ payload.reasoning = reasoning
564
+ }
565
+ if (assessment != null) {
566
+ payload.assessment = assessment
567
+ }
568
+
569
+ const currentStore = storage.getStore()
570
+ const routing = currentStore?.routingContext
571
+ evalMetricAppendCh.publish({ payload, routing })
572
+ } catch (e) {
573
+ if (e.ddErrorTag) err = e.ddErrorTag
574
+ throw e
575
+ } finally {
576
+ telemetry.recordSubmitFeedback(metricTypeTag, targetType, err)
577
+ }
578
+ }
579
+
506
580
  annotationContext (options, fn) {
507
581
  if (!this.enabled) return fn()
508
582
 
@@ -239,6 +239,14 @@ class LLMObsSpanProcessor {
239
239
 
240
240
  const apmTraceId = span.context().toTraceId(true)
241
241
  const llmobsTraceId = mlObsTags[TRACE_ID] ?? apmTraceId
242
+ const dd = {
243
+ span_id: span.context().toSpanId(),
244
+ trace_id: apmTraceId,
245
+ sample_rate: mlObsTags[SAMPLE_RATE],
246
+ sampling_decision: mlObsTags[SAMPLING_DECISION],
247
+ apm_trace_id: apmTraceId,
248
+ }
249
+ if (tags.experiment_id) dd.scope = 'experiments'
242
250
 
243
251
  const llmObsSpanEvent = {
244
252
  trace_id: llmobsTraceId,
@@ -251,13 +259,7 @@ class LLMObsSpanProcessor {
251
259
  status: error ? 'error' : 'ok',
252
260
  meta,
253
261
  metrics,
254
- _dd: {
255
- span_id: span.context().toSpanId(),
256
- trace_id: apmTraceId,
257
- sample_rate: mlObsTags[SAMPLE_RATE],
258
- sampling_decision: mlObsTags[SAMPLING_DECISION],
259
- apm_trace_id: apmTraceId,
260
- },
262
+ _dd: dd,
261
263
  }
262
264
 
263
265
  if (sessionId) llmObsSpanEvent.session_id = sessionId
@@ -158,8 +158,7 @@ class LLMObsTagger {
158
158
  if (modelName) this.tagModelName(span, modelName)
159
159
  if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
160
160
 
161
- sessionId = sessionId ||
162
- registry.get(parent)?.[SESSION_ID] ||
161
+ sessionId ||= registry.get(parent)?.[SESSION_ID] ||
163
162
  traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
164
163
  traceTags[PROPAGATED_SESSION_ID_KEY]
165
164
  if (sessionId) this._setTag(span, SESSION_ID, sessionId)
@@ -183,6 +183,23 @@ function recordSubmitEvaluation (options, err, value = 1) {
183
183
  llmobsMetrics.count('evals_submitted', tags).inc(value)
184
184
  }
185
185
 
186
+ /**
187
+ * @param {string} metricType - The submitted metric type, `'other'` if unrecognized.
188
+ * @param {string} targetType - The payload key the feedback was attached to, `'other'` if unresolved.
189
+ * @param {string} err - The telemetry error tag, empty when the submission succeeded.
190
+ * @param {number} [value]
191
+ * @returns {void}
192
+ */
193
+ function recordSubmitFeedback (metricType, targetType, err, value = 1) {
194
+ const tags = {
195
+ error: Number(!!err),
196
+ metric_type: metricType,
197
+ target_type: targetType,
198
+ }
199
+ if (err) tags.error_type = err
200
+ llmobsMetrics.count('feedback_submitted', tags).inc(value)
201
+ }
202
+
186
203
  function recordLLMObsUserProcessorCalled (error, value = 1) {
187
204
  const tags = { error: error ? 1 : 0 }
188
205
  llmobsMetrics.count('user_processor_called', tags).inc(value)
@@ -201,5 +218,6 @@ module.exports = {
201
218
  recordUserFlush,
202
219
  recordExportSpan,
203
220
  recordSubmitEvaluation,
221
+ recordSubmitFeedback,
204
222
  recordLLMObsUserProcessorCalled,
205
223
  }
@@ -88,6 +88,9 @@ class BaseLLMObsWriter {
88
88
  }
89
89
 
90
90
  /**
91
+ * @param {Record<string, unknown>} event
92
+ * @param {{ apiKey?: string, site?: string }} [routing]
93
+ * @param {number} [byteLength]
91
94
  * @returns {boolean} `true` if the event was buffered, `false` if it was dropped
92
95
  * (e.g. the per-routing buffer was full). Callers that depend on the event
93
96
  * actually being submitted should check this value.
@@ -49,12 +49,18 @@ class LLMObsSpanWriter extends BaseWriter {
49
49
  }
50
50
 
51
51
  makePayload (events) {
52
- return events.map(event => ({
53
- '_dd.stage': 'raw',
54
- '_dd.tracer_version': tracerVersion,
55
- event_type: this._eventType,
56
- spans: [event],
57
- }))
52
+ return events.map(event => {
53
+ const eventData = {
54
+ '_dd.stage': 'raw',
55
+ '_dd.tracer_version': tracerVersion,
56
+ event_type: this._eventType,
57
+ spans: [event],
58
+ }
59
+ if (event?._dd?.scope === 'experiments') {
60
+ eventData['_dd.scope'] = 'experiments'
61
+ }
62
+ return eventData
63
+ })
58
64
  }
59
65
 
60
66
  _truncateSpanEvent (event) {
@@ -7,6 +7,8 @@ const { fetchAgentInfo } = require('../../agent/info')
7
7
 
8
8
  /**
9
9
  * @param {import('../../config/config-base')} config
10
+ * @param {(agentless: boolean) => void} setWritersAgentlessValue
11
+ * @returns {void}
10
12
  */
11
13
  function setAgentStrategy (config, setWritersAgentlessValue) {
12
14
  const agentlessEnabled = config.llmobs.agentlessEnabled
@@ -2,15 +2,7 @@
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
- const Level = {
6
- trace: 10,
7
- debug: 20,
8
- info: 30,
9
- warn: 40,
10
- error: 50,
11
- critical: 50,
12
- off: 100,
13
- }
5
+ const Level = require('./levels')
14
6
 
15
7
  const traceChannel = channel('datadog:log:trace')
16
8
  const debugChannel = channel('datadog:log:debug')
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ // The guardrails logger requires this file, so it has to parse on Node.js 0.8.
4
+ module.exports = {
5
+ trace: 10,
6
+ debug: 20,
7
+ info: 30,
8
+ warn: 40,
9
+ error: 50,
10
+ critical: 50,
11
+ off: 100
12
+ }
@@ -55,7 +55,7 @@ class NoopProxy {
55
55
 
56
56
  if (typeof fn !== 'function') return
57
57
 
58
- options = options || {}
58
+ options ||= {}
59
59
 
60
60
  return this._tracer.trace(name, options, fn)
61
61
  }
@@ -68,7 +68,7 @@ class NoopProxy {
68
68
 
69
69
  if (typeof fn !== 'function') return fn
70
70
 
71
- options = options || {}
71
+ options ||= {}
72
72
 
73
73
  return this._tracer.wrap(name, options, fn)
74
74
  }
@@ -63,7 +63,7 @@ class ExposuresWriter extends BaseFFEWriter {
63
63
  * @param {import('../../config/config-base')} config - Tracer configuration object
64
64
  */
65
65
  constructor (config) {
66
- const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
66
+ const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
67
67
  const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
68
68
  const fullEndpoint = `${basePath}/${endpoint}`
69
69
 
@@ -18,7 +18,7 @@ function setAgentStrategy (config, setWriterEnabledValue) {
18
18
  }
19
19
 
20
20
  const endpoints = agentInfo.endpoints
21
- const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
21
+ const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
22
22
  const hasEndpoint = Array.isArray(endpoints) &&
23
23
  endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
24
24
 
@@ -22,7 +22,7 @@ class BatchLogRecordProcessor {
22
22
  /**
23
23
  * Creates a new BatchLogRecordProcessor instance.
24
24
  *
25
- * @param {OtlpHttpLogExporter} exporter - Log processor for exporting batches to Datadog Agent
25
+ * @param {import('./otlp_http_log_exporter')} exporter - Log processor for exporting batches to Datadog Agent
26
26
  * @param {number} batchTimeout - Timeout in milliseconds for batch processing
27
27
  * @param {number} maxExportBatchSize - Maximum number of log records per batch
28
28
  */
@@ -9,6 +9,8 @@ const { METRIC_TYPES } = require('./constants')
9
9
  /**
10
10
  * @typedef {import('@opentelemetry/api').MetricOptions} MetricOptions
11
11
  * @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
12
+ * @typedef {Counter | UpDownCounter | Histogram | Gauge |
13
+ * ObservableGauge | ObservableCounter | ObservableUpDownCounter} Instrument
12
14
  */
13
15
 
14
16
  /**
@@ -25,7 +27,7 @@ class Meter {
25
27
  /**
26
28
  * Creates a new Meter instance.
27
29
  *
28
- * @param {MeterProvider} meterProvider - Parent meter provider
30
+ * @param {import('./meter_provider')} meterProvider - Parent meter provider
29
31
  * @param {InstrumentationScope} instrumentationScope - Instrumentation scope information
30
32
  * @param {string} [instrumentationScope.name] - Meter name (defaults to 'dd-trace-js')
31
33
  * @param {string} [instrumentationScope.version] - Meter version (defaults to tracer version)
@@ -50,11 +52,13 @@ class Meter {
50
52
  * Instruments are cached by type and normalized (lowercase) name.
51
53
  *
52
54
  *
55
+ * @template {Instrument} T
53
56
  * @param {string} name - Instrument name (will be normalized to lowercase)
54
57
  * @param {string} type - Instrument type (e.g., 'counter', 'histogram', 'gauge')
55
- * @param {Function} InstrumentClass - Constructor for the instrument type
56
- * @param {MetricOptions} [options] - Instrument options (description, unit, etc.)
57
- * @returns {Instrument} The instrument instance (new or cached)
58
+ * @param {new (name: string, options: object, instrumentationScope: InstrumentationScope,
59
+ * reader: object) => T} InstrumentClass - Constructor for the instrument type
60
+ * @param {MetricOptions} options - Instrument options (description, unit, etc.)
61
+ * @returns {T} The instrument instance (new or cached)
58
62
  */
59
63
  #getOrCreateInstrument (name, type, InstrumentClass, options) {
60
64
  const normalizedName = name.toLowerCase()
@@ -198,7 +198,7 @@ class OtlpTransformer extends OtlpTransformerBase {
198
198
  if (isJson) {
199
199
  dataPoint.startTimeUnixNano = String(dataPoint.startTimeUnixNano)
200
200
  dataPoint.timeUnixNano = String(dataPoint.timeUnixNano)
201
- dataPoint.count = dataPoint.count || 0
201
+ dataPoint.count ||= 0
202
202
  }
203
203
 
204
204
  return dataPoint
@@ -109,7 +109,7 @@ class PeriodicMetricReader {
109
109
  /**
110
110
  * Creates a new PeriodicMetricReader instance.
111
111
  *
112
- * @param {OtlpHttpMetricExporter} exporter - Metric exporter for sending to Datadog Agent
112
+ * @param {import('./otlp_http_metric_exporter')} exporter - Metric exporter for sending to Datadog Agent
113
113
  * @param {number} exportInterval - Export interval in milliseconds
114
114
  * @param {string} temporalityPreference - Temporality preference: DELTA, CUMULATIVE, or LOWMEMORY
115
115
  * @param {number} maxBatchedQueueSize - Maximum number of measurements to queue before dropping
@@ -253,7 +253,8 @@ class PeriodicMetricReader {
253
253
  #collectAndExport (callback = () => {}) {
254
254
  // Atomically drain measurements for export. New measurements can be recorded
255
255
  // during export without interfering with this batch.
256
- const allMeasurements = this.#measurements.splice(0)
256
+ const allMeasurements = this.#measurements
257
+ this.#measurements = []
257
258
 
258
259
  for (const instrument of this.observableInstruments) {
259
260
  const observableMeasurements = instrument.collect()
@@ -38,6 +38,10 @@ function hrTimeToMilliseconds (hrTime) {
38
38
  /**
39
39
  * Several of these attributes are not yet supported by the Node.js OTel API.
40
40
  * We check for old equivalents where we can, but not all had equivalents.
41
+ *
42
+ * @param {string | undefined} spanName
43
+ * @param {import('@opentelemetry/api').SpanKind} kind
44
+ * @param {import('@opentelemetry/api').Attributes} attributes
41
45
  */
42
46
  function spanNameMapper (spanName, kind, attributes) {
43
47
  if (spanName) return spanName
@@ -440,7 +440,7 @@ class DatadogSpan {
440
440
  }
441
441
  }
442
442
 
443
- spanContext._trace.ticks = spanContext._trace.ticks || now()
443
+ spanContext._trace.ticks ||= now()
444
444
  if (startTime) {
445
445
  spanContext._trace.startTime = startTime
446
446
  }
@@ -8,7 +8,7 @@ const TRACE_ID_128 = '_dd.p.tid'
8
8
 
9
9
  class DatadogSpanContext {
10
10
  constructor (props) {
11
- props = props || {}
11
+ props ||= {}
12
12
 
13
13
  this._traceId = props.traceId
14
14
  this._spanId = props.spanId