dd-trace 6.20.1 → 6.22.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 (225) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
@@ -22,6 +22,7 @@ const { getLocalStateForCallFrame, evaluateCaptureExpressions } = require('./sna
22
22
  const send = require('./send')
23
23
  const { getStackFromCallFrames } = require('./state')
24
24
  const { ackEmitting } = require('./status')
25
+ const WorkerError = require('./worker-error')
25
26
  const config = require('./config')
26
27
  const log = require('./log')
27
28
 
@@ -51,9 +52,10 @@ session.on('Debugger.paused', async ({ params }) => {
51
52
 
52
53
  if (params.reason !== 'other') {
53
54
  // This error should not be caught, and should exit the worker thread, effectively stopping the debugging session
54
- throw Object.assign(new Error(`Unexpected Debugger.paused reason: ${params.reason}`), {
55
- reason: WORKER_ERROR_REASON.UNEXPECTED_PAUSE_REASON,
56
- })
55
+ throw new WorkerError(
56
+ `Unexpected Debugger.paused reason: ${params.reason}`,
57
+ WORKER_ERROR_REASON.UNEXPECTED_PAUSE_REASON
58
+ )
57
59
  }
58
60
 
59
61
  let maxReferenceDepth = 0
@@ -4,6 +4,7 @@ const { workerData: { probePort } } = require('node:worker_threads')
4
4
  const { WORKER_ERROR_REASON } = require('../constants')
5
5
  const { addBreakpoint, removeBreakpoint, modifyBreakpoint } = require('./breakpoints')
6
6
  const { ackReceived, ackInstalled, ackError } = require('./status')
7
+ const WorkerError = require('./worker-error')
7
8
  const log = require('./log')
8
9
 
9
10
  // Example log line probe with captureSnapshot (simplified):
@@ -74,20 +75,23 @@ async function processMsg (action, probe) {
74
75
  if (action !== 'unapply') ackReceived(probe)
75
76
 
76
77
  if (probe.type !== 'LOG_PROBE') {
77
- throw Object.assign(new Error(
78
- `Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`
79
- ), { reason: WORKER_ERROR_REASON.UNSUPPORTED_PROBE_TYPE })
78
+ throw new WorkerError(
79
+ `Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`,
80
+ WORKER_ERROR_REASON.UNSUPPORTED_PROBE_TYPE
81
+ )
80
82
  }
81
83
  if (!probe.where.sourceFile && !probe.where.lines) {
82
- throw Object.assign(new Error(
84
+ throw new WorkerError(
83
85
  // eslint-disable-next-line @stylistic/max-len
84
- `Unsupported probe insertion point! Only line-based probes are supported (id: ${probe.id}, version: ${probe.version})`
85
- ), { reason: WORKER_ERROR_REASON.UNSUPPORTED_INSERTION_POINT })
86
+ `Unsupported probe insertion point! Only line-based probes are supported (id: ${probe.id}, version: ${probe.version})`,
87
+ WORKER_ERROR_REASON.UNSUPPORTED_INSERTION_POINT
88
+ )
86
89
  }
87
90
  if (probe.captureSnapshot && probe.captureExpressions?.length > 0) {
88
- throw Object.assign(new Error(
89
- `Cannot set both captureSnapshot and captureExpressions (probe: ${probe.id}, version: ${probe.version})`
90
- ), { reason: WORKER_ERROR_REASON.CONFLICTING_CAPTURE_OPTIONS })
91
+ throw new WorkerError(
92
+ `Cannot set both captureSnapshot and captureExpressions (probe: ${probe.id}, version: ${probe.version})`,
93
+ WORKER_ERROR_REASON.CONFLICTING_CAPTURE_OPTIONS
94
+ )
91
95
  }
92
96
 
93
97
  switch (action) {
@@ -103,8 +107,9 @@ async function processMsg (action, probe) {
103
107
  ackInstalled(probe)
104
108
  break
105
109
  default:
106
- throw Object.assign(new Error(
107
- `Cannot process probe ${probe.id} (version: ${probe.version}) - unknown remote configuration action: ${action}`
108
- ), { reason: WORKER_ERROR_REASON.UNKNOWN_REMOTE_CONFIG_ACTION })
110
+ throw new WorkerError(
111
+ `Cannot process probe ${probe.id} (version: ${probe.version}) - unknown remote configuration action: ${action}`,
112
+ WORKER_ERROR_REASON.UNKNOWN_REMOTE_CONFIG_ACTION
113
+ )
109
114
  }
110
115
  }
@@ -17,8 +17,12 @@ const buildTags = require('./tags')
17
17
 
18
18
  module.exports = send
19
19
 
20
- const MAX_LOG_PAYLOAD_SIZE_MB = 1
21
- const MAX_LOG_PAYLOAD_SIZE_BYTES = MAX_LOG_PAYLOAD_SIZE_MB * 1024 * 1024
20
+ // The backend caps a single debugger event at 1 MiB, but measures the CBOR-encoded event *after* the
21
+ // intake has enriched it with host tags, `ddtags` and other metadata we cannot size from here. Leave
22
+ // the same 4 KiB of headroom as dd-trace-go: crossing the backend cap is worse than pruning, since
23
+ // its truncation destroys the log message before it drops the snapshot.
24
+ const BACKEND_ENRICHMENT_HEADROOM_BYTES = 4 * 1024
25
+ const MAX_LOG_PAYLOAD_SIZE_BYTES = 1024 * 1024 - BACKEND_ENRICHMENT_HEADROOM_BYTES
22
26
 
23
27
  const ddsource = 'dd_debugger'
24
28
  const hostname = getHostname()
@@ -57,6 +61,7 @@ function send (message, logger, dd, snapshot, processTags, eventType, incomplete
57
61
  ddsource,
58
62
  hostname,
59
63
  service,
64
+ timestamp: snapshot.timestamp,
60
65
  message,
61
66
  logger,
62
67
  dd,
@@ -129,6 +129,7 @@ function statusPayload (probeId, probeVersion, status) {
129
129
  return {
130
130
  ddsource,
131
131
  service,
132
+ timestamp: Date.now(),
132
133
  debugger: {
133
134
  diagnostics: { probeId, runtimeId, probeVersion, status },
134
135
  },
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ /** @typedef {typeof import('../constants').WORKER_ERROR_REASON} WorkerErrorReasons */
4
+
5
+ /**
6
+ * An error carrying a failure reason for telemetry. Its prototype does not survive the worker boundary, so the main
7
+ * thread must read `reason` instead of checking `instanceof`. `name` is left as `Error` because structured cloning
8
+ * only keeps built-in error names, and telemetry should report the same name however the error leaves the worker.
9
+ */
10
+ class WorkerError extends Error {
11
+ /**
12
+ * @param {string} message - The error message
13
+ * @param {WorkerErrorReasons[keyof WorkerErrorReasons]} reason - The failure reason reported in telemetry
14
+ */
15
+ constructor (message, reason) {
16
+ super(message)
17
+ this.reason = reason
18
+ }
19
+ }
20
+
21
+ module.exports = WorkerError
@@ -9,10 +9,10 @@
9
9
  * atomics, and the main thread periodically drains the counters into instrumentation telemetry metrics.
10
10
  *
11
11
  * The metric names, tags and reason codes are defined by the "Debugger Observability for GA" specification and are
12
- * shared with the other tracers. All metrics are counts in the `live_debugger` telemetry namespace.
12
+ * shared with the other tracers. All metrics are counts in the `debugger` telemetry namespace.
13
13
  */
14
14
 
15
- const TELEMETRY_NAMESPACE = 'live_debugger'
15
+ const TELEMETRY_NAMESPACE = 'debugger'
16
16
 
17
17
  const EVENT_TYPE = Object.freeze({
18
18
  SNAPSHOT: 0,
@@ -8,6 +8,7 @@ const dc = require('dc-polyfill')
8
8
  const log = require('../log')
9
9
  const { fetchAgentInfo } = require('../agent/info')
10
10
  const telemetryMetrics = require('../telemetry/metrics')
11
+ const { logGenericError } = require('../startup-log')
11
12
  const getDebuggerConfig = require('./config')
12
13
  const {
13
14
  DEBUGGER_DIAGNOSTICS_V1,
@@ -15,6 +16,7 @@ const {
15
16
  DEBUGGER_INPUT_V2,
16
17
  INSPECT_SEGMENT_GLOBAL_PROPERTY,
17
18
  METRICS_FLUSH_INTERVAL_MS,
19
+ WORKER_ERROR_REASON,
18
20
  } = require('./constants')
19
21
  const { GuardrailMetrics, TELEMETRY_NAMESPACE } = require('./guardrail-metrics')
20
22
  const { PauseDurationHistogram } = require('./pause-duration-histogram')
@@ -33,6 +35,8 @@ const { configureProbeSampler, installProbeSampler, uninstallProbeSampler } = re
33
35
  // be lost when the application exits on its own.
34
36
  const TELEMETRY_APP_CLOSING_CHANNEL = 'datadog:telemetry:app-closing'
35
37
 
38
+ const WORKER_FILENAME = join(__dirname, 'devtools_client', 'index.js')
39
+
36
40
  let worker = null
37
41
  let configChannel = null
38
42
  let ackId = 0
@@ -139,7 +143,7 @@ function start (config, rcInstance) {
139
143
  inputPath = _inputPath
140
144
 
141
145
  worker = new Worker(
142
- join(__dirname, 'devtools_client', 'index.js'),
146
+ WORKER_FILENAME,
143
147
  {
144
148
  name: 'dd-debugger',
145
149
  execArgv: [], // Avoid worker thread inheriting the `-r` command line argument
@@ -165,7 +169,7 @@ function start (config, rcInstance) {
165
169
  )
166
170
  })
167
171
 
168
- worker.on('error', (err) => logWorkerError(err))
172
+ worker.on('error', onWorkerError)
169
173
  worker.on('messageerror', (err) => log.error('[debugger] received "messageerror" from worker', err))
170
174
 
171
175
  worker.once('exit', (code) => {
@@ -186,6 +190,24 @@ function start (config, rcInstance) {
186
190
  })
187
191
  }
188
192
 
193
+ /**
194
+ * @param {Error & { code?: unknown, requireStack?: unknown }} error - The error emitted by the worker thread
195
+ */
196
+ function onWorkerError (error) {
197
+ // Node.js reports an empty require stack when the module it cannot find is the worker entry point itself, rather
198
+ // than one of its dependencies. This happens when the dd-trace files are not on disk where dd-trace expects them,
199
+ // e.g. because dd-trace was bundled into the application, or because some of its files were not deployed.
200
+ if (error.code === 'MODULE_NOT_FOUND' && Array.isArray(error.requireStack) && error.requireStack.length === 0) {
201
+ logGenericError(
202
+ `Dynamic Instrumentation is disabled, because its worker thread could not load ${WORKER_FILENAME}. ` +
203
+ 'Make sure dd-trace is installed as regular files on disk and is not bundled into the application.'
204
+ )
205
+ logWorkerError(error, WORKER_ERROR_REASON.WORKER_ENTRY_POINT_NOT_FOUND)
206
+ return
207
+ }
208
+ logWorkerError(error)
209
+ }
210
+
189
211
  /**
190
212
  * Failure metadata belongs in the telemetry message; exception messages remain in the redacted cause.
191
213
  *
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { types } = require('node:util')
4
4
 
5
+ const { storage } = require('../../../datadog-core')
5
6
  const { MAX_SNAPSHOTS_PER_SECOND_GLOBALLY } = require('./devtools_client/defaults')
6
7
  const { MAX_MESSAGE_LENGTH } = require('./constants')
7
8
  const { EVENT_TYPE, SKIPPED_REASON } = require('./guardrail-metrics')
@@ -32,6 +33,20 @@ const ddTraceGlobal = /** @type {Record<symbol, SharedArrayBuffer | object | und
32
33
  * @property {{ DD_DYNAMIC_INSTRUMENTATION_EVALUATION_TIMEOUT_MS: number }} dynamicInstrumentation
33
34
  */
34
35
 
36
+ /**
37
+ * The trace object shared by all spans of a trace in this process (`DatadogSpanContext#_trace`).
38
+ *
39
+ * @typedef {{ started: object[] }} LocalTrace
40
+ */
41
+
42
+ /**
43
+ * The span active in the current async context, as stored in the legacy storage by the tracer.
44
+ *
45
+ * @typedef {{ context: () => { _trace: LocalTrace } }} ActiveSpan
46
+ */
47
+
48
+ const legacyStorage = storage('legacy')
49
+
35
50
  let evaluationTimeoutNs = 0n
36
51
 
37
52
  module.exports = {
@@ -63,6 +78,14 @@ function installProbeSampler (guardrailMetrics, config) {
63
78
  const buffer = createProbeSamplerBuffer()
64
79
 
65
80
  const lastCaptureNsByProbeId = new Map()
81
+ /**
82
+ * The coordinated sampling decision of each trace in which a snapshot-producing probe was hit: `false` if the trace
83
+ * was dropped, otherwise the sampling indexes of the probes that already emitted a snapshot in it. Keyed weakly, so
84
+ * a decision is released together with its trace.
85
+ *
86
+ * @type {WeakMap<LocalTrace, Set<number> | false>}
87
+ */
88
+ const emittedProbesByTrace = new WeakMap()
66
89
  /**
67
90
  * Probes that are skipped at entry because a recent evaluation failed or exceeded its time budget. One error result
68
91
  * is reported per throttle window, so the probe stays visible without repeatedly paying for the evaluation.
@@ -219,7 +242,10 @@ function installProbeSampler (guardrailMetrics, config) {
219
242
  }
220
243
 
221
244
  /**
222
- * Apply the per-probe and global rate limits and store the sampled probe index for the debugger worker.
245
+ * Decide if a probe hit should be sampled and store the sampled probe index for the debugger worker.
246
+ *
247
+ * Snapshot-producing probes hit within an active trace share one sampling decision per trace, so a trace emits the
248
+ * snapshots of all of its probes or none of them. Other hits are sampled independently.
223
249
  *
224
250
  * @param {number} probeIndex - The worker-side probe sampling index.
225
251
  * @param {string} probeId - The probe id.
@@ -228,29 +254,100 @@ function installProbeSampler (guardrailMetrics, config) {
228
254
  * @param {boolean} isSnapshotProducingProbe - Whether this probe counts toward the global snapshot sample limit.
229
255
  */
230
256
  function sample (probeIndex, probeId, now, nsBetweenSampling, isSnapshotProducingProbe) {
257
+ if (isSnapshotProducingProbe === true) {
258
+ const span = /** @type {ActiveSpan | null | undefined} */ (legacyStorage.getStore()?.span)
259
+ const trace = span?.context()._trace
260
+ // The span processor empties `started` once all spans of a trace have finished. A probe hit under such a trace
261
+ // runs in a context that outlived it, e.g. a connection pool or event emitter callback bound to a request that
262
+ // has since ended. Coordinating that hit would let a decision made for the old request silence the probe, or
263
+ // limit it to a single snapshot, for as long as the context lives, so it's sampled independently instead.
264
+ if (trace !== undefined && trace.started.length !== 0) {
265
+ return sampleInTrace(trace, probeIndex, probeId, now, nsBetweenSampling)
266
+ }
267
+ }
268
+
269
+ if (isRateLimited(probeId, now, nsBetweenSampling, isSnapshotProducingProbe)) return false
270
+ return commitSample(probeIndex, probeId, now, isSnapshotProducingProbe)
271
+ }
272
+
273
+ /**
274
+ * Sample a snapshot-producing probe hit using the sampling decision of its trace. The first such probe hit in the
275
+ * trace makes the decision for all of them, subject to its per-probe rate limit and the global snapshot rate limit.
276
+ * If the trace is sampled, each probe then emits once in it without being subject to the rate limits, so the trace
277
+ * emits a complete set of snapshots. Those snapshots still count toward the rate limits, which keeps the overall
278
+ * volume close to the limits by making later traces less likely to be sampled.
279
+ *
280
+ * @param {LocalTrace} trace - The trace the probe was hit in.
281
+ * @param {number} probeIndex - The worker-side probe sampling index.
282
+ * @param {string} probeId - The probe id.
283
+ * @param {bigint} now - The current time.
284
+ * @param {bigint} nsBetweenSampling - Minimum nanoseconds between samples for this probe.
285
+ */
286
+ function sampleInTrace (trace, probeIndex, probeId, now, nsBetweenSampling) {
287
+ const emittedProbes = emittedProbesByTrace.get(trace)
288
+
289
+ if (emittedProbes === undefined) {
290
+ if (isRateLimited(probeId, now, nsBetweenSampling, true)) {
291
+ emittedProbesByTrace.set(trace, false)
292
+ return false
293
+ }
294
+ // If the sampled probe can't be handed over, no decision is made, so the next probe hit in the trace makes it
295
+ if (!commitSample(probeIndex, probeId, now, true)) return false
296
+ emittedProbesByTrace.set(trace, new Set([probeIndex]))
297
+ return true
298
+ }
299
+
300
+ if (emittedProbes === false || emittedProbes.has(probeIndex)) {
301
+ guardrailMetrics.eventSkipped(SKIPPED_REASON.RATE_LIMIT_PROBE, EVENT_TYPE.SNAPSHOT)
302
+ return false
303
+ }
304
+
305
+ if (!commitSample(probeIndex, probeId, now, true)) return false
306
+ emittedProbes.add(probeIndex)
307
+ return true
308
+ }
309
+
310
+ /**
311
+ * Check the per-probe and global rate limits, recording the skip if a limit is reached.
312
+ *
313
+ * @param {string} probeId - The probe id.
314
+ * @param {bigint} now - The current time.
315
+ * @param {bigint} nsBetweenSampling - Minimum nanoseconds between samples for this probe.
316
+ * @param {boolean} isSnapshotProducingProbe - Whether this probe is subject to the global snapshot sample limit.
317
+ */
318
+ function isRateLimited (probeId, now, nsBetweenSampling, isSnapshotProducingProbe) {
231
319
  const lastCaptureNs = lastCaptureNsByProbeId.get(probeId)
232
320
  if (lastCaptureNs !== undefined && now - lastCaptureNs < nsBetweenSampling) {
233
321
  guardrailMetrics.eventSkipped(
234
322
  SKIPPED_REASON.RATE_LIMIT_PROBE,
235
323
  isSnapshotProducingProbe === true ? EVENT_TYPE.SNAPSHOT : EVENT_TYPE.LOG
236
324
  )
237
- return false
325
+ return true
238
326
  }
239
327
 
240
- let shouldResetGlobalSnapshotRateWindow = false
241
- if (isSnapshotProducingProbe === true) {
242
- if (now - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
243
- shouldResetGlobalSnapshotRateWindow = true
244
- } else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
245
- guardrailMetrics.eventSkipped(SKIPPED_REASON.RATE_LIMIT_GLOBAL, EVENT_TYPE.SNAPSHOT)
246
- return false
247
- }
328
+ if (isSnapshotProducingProbe === true &&
329
+ now - globalSnapshotSamplingRateWindowStart <= oneSecondNs &&
330
+ snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
331
+ guardrailMetrics.eventSkipped(SKIPPED_REASON.RATE_LIMIT_GLOBAL, EVENT_TYPE.SNAPSHOT)
332
+ return true
248
333
  }
249
334
 
335
+ return false
336
+ }
337
+
338
+ /**
339
+ * Store the sampled probe index for the debugger worker and count the sample toward the rate limits.
340
+ *
341
+ * @param {number} probeIndex - The worker-side probe sampling index.
342
+ * @param {string} probeId - The probe id.
343
+ * @param {bigint} now - The current time.
344
+ * @param {boolean} isSnapshotProducingProbe - Whether this probe counts toward the global snapshot sample limit.
345
+ */
346
+ function commitSample (probeIndex, probeId, now, isSnapshotProducingProbe) {
250
347
  if (!storeSampledProbeIndex(probeIndex)) return false
251
348
 
252
349
  if (isSnapshotProducingProbe === true) {
253
- if (shouldResetGlobalSnapshotRateWindow === true) {
350
+ if (now - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
254
351
  snapshotsSampledWithinTheLastSecond = 1
255
352
  globalSnapshotSamplingRateWindowStart = now
256
353
  } else {
@@ -533,7 +533,7 @@ class DogStatsDClient {
533
533
  for (const [key, value] of Object.entries(config.tags)) {
534
534
  // Skip runtime-id unless enabled as cardinality may be too high
535
535
  if (typeof value === 'string' && (key !== 'runtime-id' || config.runtimeMetricsRuntimeId)) {
536
- // https://docs.datadoghq.com/tagging/#defining-tags
536
+ // https://docs.datadoghq.com/getting_started/tagging/#define-tags
537
537
  const valueStripped = value.replaceAll(/[^a-z0-9_:./-]/ig, '_')
538
538
 
539
539
  tags.push(`${key}:${valueStripped}`)
@@ -2,7 +2,7 @@
2
2
 
3
3
  // From agent truncators: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/agent/truncator.go
4
4
 
5
- // Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/truncate.go#L22-L27
5
+ // Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/truncate.go
6
6
  // MAX_RESOURCE_NAME_LENGTH the maximum length a span resource can have
7
7
  const MAX_RESOURCE_NAME_LENGTH = 5000
8
8
  // MAX_META_KEY_LENGTH the maximum length of metadata key
@@ -15,7 +15,7 @@ const MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION = 5000
15
15
  const MAX_METRIC_KEY_LENGTH = MAX_META_KEY_LENGTH
16
16
 
17
17
  // From agent normalizer:
18
- // https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize.go
18
+ // https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/normalize.go
19
19
  // DEFAULT_SPAN_NAME is the default name we assign a span if it's missing and we have no reasonable fallback
20
20
  const DEFAULT_SPAN_NAME = 'unnamed_operation'
21
21
  // DEFAULT_SERVICE_NAME is the default name we assign a service if it's missing and we have no reasonable fallback
@@ -6,6 +6,7 @@
6
6
  const { Readable } = require('stream')
7
7
  const http = require('http')
8
8
  const https = require('https')
9
+ const { URL } = require('url')
9
10
  const zlib = require('zlib')
10
11
 
11
12
  const { storage } = require('../../../../datadog-core')
@@ -85,11 +86,7 @@ function request (data, options, callback) {
85
86
  const timeout = options.timeout || 2000
86
87
  const isSecure = options.protocol === 'https:'
87
88
  const client = isSecure ? https : http
88
- let dataArray = data
89
-
90
- if (!Array.isArray(data)) {
91
- dataArray = [data]
92
- }
89
+ const dataArray = Array.isArray(data) ? data : [data]
93
90
  const contentLength = byteLength(dataArray)
94
91
  options.headers['Content-Length'] = contentLength
95
92
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const net = require('node:net')
4
- const { format } = require('node:url')
4
+ const { format, URL } = require('node:url')
5
5
 
6
6
  const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
7
7
 
@@ -2,17 +2,15 @@
2
2
 
3
3
  const { randomFillSync } = require('crypto')
4
4
 
5
- const { channel } = require('dc-polyfill')
6
-
7
5
  const UINT_MAX = 4_294_967_296
8
6
 
9
7
  const data = new Uint8Array(8 * 8192)
10
8
  const zeroId = new Uint8Array(8)
9
+ // Reused for hex encoding: toString('hex') copies out, so the bytes need not outlive the call.
10
+ const hexScratch = Buffer.alloc(16)
11
11
 
12
12
  let batch = 0
13
13
 
14
- channel('datadog:identity:update').subscribe(reseed)
15
-
16
14
  // Internal representation of a trace or span ID.
17
15
  class Identifier {
18
16
  /** @type {number[] | Uint8Array} */
@@ -39,7 +37,7 @@ class Identifier {
39
37
  */
40
38
  toString (radix = 16) {
41
39
  if (radix === 16) {
42
- this.#stringHex ??= Buffer.from(this.#buffer).toString('hex')
40
+ this.#stringHex ??= toHexString(this.#buffer)
43
41
  return this.#stringHex
44
42
  }
45
43
  if (radix === 10) {
@@ -194,6 +192,17 @@ function toNumberString (buffer, radix) {
194
192
  return str
195
193
  }
196
194
 
195
+ // Same string as Buffer.from(buffer).toString('hex'), without allocating a Buffer per call.
196
+ /**
197
+ * @param {number[] | Uint8Array} buffer
198
+ */
199
+ function toHexString (buffer) {
200
+ const length = buffer.length
201
+ if (length > hexScratch.length) return Buffer.from(buffer).toString('hex')
202
+ for (let i = 0; i < length; i++) hexScratch[i] = buffer[i]
203
+ return hexScratch.toString('hex', 0, length)
204
+ }
205
+
197
206
  // Simple pseudo-random 64-bit ID generator.
198
207
  /**
199
208
  * @returns {number[] | Uint8Array}
@@ -267,3 +276,4 @@ module.exports = function createIdentifier (value, radix) {
267
276
  }
268
277
 
269
278
  module.exports.Identifier = Identifier
279
+ module.exports.reseed = reseed