dd-trace 5.108.0 → 5.110.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 (174) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +88 -25
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +19 -14
  5. package/packages/datadog-esbuild/index.js +26 -0
  6. package/packages/datadog-esbuild/src/utils.js +46 -3
  7. package/packages/datadog-instrumentations/src/ai.js +43 -48
  8. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
  9. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/electron.js +1 -1
  13. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  14. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  18. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  20. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  24. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  25. package/packages/datadog-instrumentations/src/ioredis.js +0 -1
  26. package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
  27. package/packages/datadog-instrumentations/src/koa.js +3 -2
  28. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  29. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  30. package/packages/datadog-instrumentations/src/next.js +44 -10
  31. package/packages/datadog-instrumentations/src/openai.js +77 -18
  32. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  34. package/packages/datadog-instrumentations/src/redis.js +0 -1
  35. package/packages/datadog-instrumentations/src/restify.js +4 -3
  36. package/packages/datadog-instrumentations/src/router.js +18 -5
  37. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  38. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  39. package/packages/datadog-instrumentations/src/vitest.js +323 -7
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
  44. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
  45. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
  46. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
  47. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
  48. package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
  49. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  50. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
  52. package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
  53. package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
  54. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  55. package/packages/datadog-plugin-fs/src/index.js +1 -0
  56. package/packages/datadog-plugin-http/src/client.js +8 -3
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  59. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  60. package/packages/datadog-plugin-redis/src/index.js +1 -2
  61. package/packages/datadog-plugin-undici/src/index.js +5 -2
  62. package/packages/datadog-plugin-vitest/src/index.js +11 -2
  63. package/packages/datadog-webpack/index.js +17 -1
  64. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  65. package/packages/dd-trace/src/aiguard/channels.js +0 -1
  66. package/packages/dd-trace/src/aiguard/index.js +11 -49
  67. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
  68. package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
  69. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
  70. package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
  71. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
  72. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  73. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  74. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  75. package/packages/dd-trace/src/appsec/channels.js +1 -0
  76. package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  78. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  79. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  80. package/packages/dd-trace/src/appsec/index.js +10 -6
  81. package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
  82. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  83. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  84. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  86. package/packages/dd-trace/src/config/defaults.js +22 -3
  87. package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
  88. package/packages/dd-trace/src/config/helper.js +133 -33
  89. package/packages/dd-trace/src/config/index.js +4 -1
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +45 -5
  92. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
  93. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  95. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  96. package/packages/dd-trace/src/debugger/index.js +5 -0
  97. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  98. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  99. package/packages/dd-trace/src/dogstatsd.js +15 -8
  100. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  101. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  102. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  103. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  104. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  105. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  106. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  107. package/packages/dd-trace/src/guardrails/index.js +8 -5
  108. package/packages/dd-trace/src/index.js +6 -4
  109. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  110. package/packages/dd-trace/src/llmobs/index.js +16 -5
  111. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  112. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  113. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  114. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  115. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  116. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  117. package/packages/dd-trace/src/log/index.js +18 -20
  118. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
  119. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  120. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  121. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  122. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  123. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  124. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  125. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  126. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  127. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  128. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  129. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  130. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  131. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  132. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  133. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  134. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  135. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  136. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  137. package/packages/dd-trace/src/plugin_manager.js +7 -1
  138. package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
  139. package/packages/dd-trace/src/plugins/index.js +3 -0
  140. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  141. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  142. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  143. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  144. package/packages/dd-trace/src/priority_sampler.js +1 -13
  145. package/packages/dd-trace/src/profiling/config.js +129 -198
  146. package/packages/dd-trace/src/profiling/constants.js +4 -7
  147. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  148. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  149. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  150. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  151. package/packages/dd-trace/src/profiling/oom.js +71 -0
  152. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  153. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  154. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  155. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  156. package/packages/dd-trace/src/ritm.js +4 -2
  157. package/packages/dd-trace/src/serverless.js +1 -2
  158. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  159. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  162. package/packages/dd-trace/src/span_format.js +2 -1
  163. package/packages/dd-trace/src/span_processor.js +6 -0
  164. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  165. package/packages/dd-trace/src/tracer.js +15 -1
  166. package/packages/dd-trace/src/util.js +14 -0
  167. package/register.js +58 -1
  168. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  169. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  170. package/vendor/dist/pprof-format/index.js +1 -1
  171. package/vendor/dist/protobufjs/index.js +1 -1
  172. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  173. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
  174. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ const { DD_TRACE_SYMBOL, PROBE_SAMPLER_SYMBOL } = require('../probe_sampler_constants')
4
+
5
+ const SAMPLER_EXPRESSION = `globalThis[Symbol.for(${JSON.stringify(DD_TRACE_SYMBOL)})]?.` +
6
+ `[Symbol.for(${JSON.stringify(PROBE_SAMPLER_SYMBOL)})]`
7
+
8
+ module.exports = {
9
+ compileBreakpointCondition,
10
+ getRemoveProbeExpression,
11
+ }
12
+
13
+ /**
14
+ * Build the expression that removes a probe from runtime sampler state. Called by the devtools worker and evaluated on
15
+ * the debuggee.
16
+ *
17
+ * @param {string} id - The probe id.
18
+ * @returns {string}
19
+ */
20
+ function getRemoveProbeExpression (id) {
21
+ return `${SAMPLER_EXPRESSION}?.remove(${JSON.stringify(id)})`
22
+ }
23
+
24
+ /**
25
+ * Build a Chrome DevTools breakpoint condition that samples all matching probes at a location. Called by the devtools
26
+ * worker.
27
+ *
28
+ * @param {{
29
+ * id: string,
30
+ * samplingIndex: number,
31
+ * nsBetweenSampling: bigint,
32
+ * condition?: string,
33
+ * captureSnapshot?: boolean,
34
+ * compiledCaptureExpressions?: object[]
35
+ * }[]} probes - The probes at the breakpoint location.
36
+ * @returns {string}
37
+ */
38
+ function compileBreakpointCondition (probes) {
39
+ const probeConditions = []
40
+ for (const probe of probes) {
41
+ probeConditions.push(compileProbeCondition(probe))
42
+ }
43
+
44
+ // NOTE: $dd_sampler is read from the realm-local `globalThis` where it was installed (the main
45
+ // realm). A probe whose code runs in a different V8 realm (e.g. a `vm.createContext` script with a
46
+ // file-path filename) won't see it and will silently never fire. Known limitation: a breakpoint
47
+ // condition has no realm-independent handle to reach, so we degrade rather than crash.
48
+ return `(() => {
49
+ const $dd_sampler = ${SAMPLER_EXPRESSION}
50
+ if ($dd_sampler === undefined) return false
51
+ let $dd_sampled = false
52
+ ${probeConditions.join('\n ')}
53
+ return $dd_sampled
54
+ })()`
55
+ }
56
+
57
+ /**
58
+ * Build the condition fragment for a single probe. Called by the devtools worker while building breakpoint conditions.
59
+ *
60
+ * @param {{
61
+ * id: string,
62
+ * samplingIndex: number,
63
+ * nsBetweenSampling: bigint,
64
+ * condition?: string,
65
+ * captureSnapshot?: boolean,
66
+ * compiledCaptureExpressions?: object[]
67
+ * }} probe - The probe to sample.
68
+ * @returns {string}
69
+ */
70
+ function compileProbeCondition (probe) {
71
+ const sample = `$dd_sampler.makeSampleDecision(${probe.samplingIndex}, ${JSON.stringify(probe.id)}, ` +
72
+ `${probe.nsBetweenSampling}n, ${probe.captureSnapshot === true || probe.compiledCaptureExpressions !== undefined})`
73
+
74
+ if (probe.condition === undefined) {
75
+ return `$dd_sampled = ${sample} || $dd_sampled`
76
+ }
77
+
78
+ return `try {
79
+ if ((${probe.condition}) === true) {
80
+ $dd_sampled = ${sample} || $dd_sampled
81
+ }
82
+ } catch {}`
83
+ }
@@ -33,6 +33,7 @@ module.exports = {
33
33
  locationToBreakpoint: new Map(),
34
34
  breakpointToProbes: new Map(),
35
35
  probeToLocation: new Map(),
36
+ samplingIndexToProbe: new Map(),
36
37
 
37
38
  _loadedScripts: loadedScripts, // Only exposed for testing
38
39
  _scriptUrls: scriptUrls, // Only exposed for testing
@@ -8,6 +8,7 @@ const log = require('../log')
8
8
  const { fetchAgentInfo } = require('../agent/info')
9
9
  const getDebuggerConfig = require('./config')
10
10
  const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2 } = require('./constants')
11
+ const { installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
11
12
 
12
13
  /**
13
14
  * @typedef {ReturnType<import('../config')>} Config
@@ -65,6 +66,8 @@ function start (config, rcInstance) {
65
66
 
66
67
  globalThis[Symbol.for('dd-trace')].utilTypes = types
67
68
 
69
+ const probeSamplerBuffer = installProbeSampler()
70
+
68
71
  readProbeFile(config.dynamicInstrumentation.probeFile, (probes) => {
69
72
  const action = 'apply'
70
73
  for (const probe of probes) {
@@ -110,6 +113,7 @@ function start (config, rcInstance) {
110
113
  probePort: probeChannel.port1,
111
114
  logPort: logChannel.port1,
112
115
  configPort: configChannel.port1,
116
+ probeSamplerBuffer,
113
117
  },
114
118
  transferList: [probeChannel.port1, logChannel.port1, configChannel.port1],
115
119
  }
@@ -187,6 +191,7 @@ function cleanup (error) {
187
191
  worker.removeAllListeners()
188
192
  worker = null
189
193
  }
194
+ uninstallProbeSampler()
190
195
  configChannel = null
191
196
  inputPath = null
192
197
 
@@ -0,0 +1,112 @@
1
+ 'use strict'
2
+
3
+ const { MAX_SNAPSHOTS_PER_SECOND_GLOBALLY } = require('./devtools_client/defaults')
4
+ const {
5
+ DD_TRACE_SYMBOL,
6
+ MAX_SAMPLED_PROBES_PER_PAUSE,
7
+ PROBE_SAMPLER_SYMBOL,
8
+ SAMPLED_PROBE_COUNT_INDEX,
9
+ SAMPLED_PROBE_INDEXES_START,
10
+ SAMPLED_PROBE_OVERFLOW_INDEX,
11
+ } = require('./probe_sampler_constants')
12
+
13
+ const ddTraceGlobal = /** @type {Record<symbol, SharedArrayBuffer | object | undefined>} */ (
14
+ /** @type {Record<symbol, unknown>} */ (globalThis)[Symbol.for(DD_TRACE_SYMBOL)]
15
+ )
16
+
17
+ module.exports = {
18
+ installProbeSampler,
19
+ uninstallProbeSampler,
20
+ }
21
+
22
+ /**
23
+ * Install the runtime sampler in the debuggee context.
24
+ *
25
+ * @returns {SharedArrayBuffer} The shared sampler buffer to pass to the debugger worker.
26
+ */
27
+ function installProbeSampler () {
28
+ const buffer = createProbeSamplerBuffer()
29
+
30
+ const lastCaptureNsByProbeId = new Map()
31
+ const sampledProbeIndexes = new Int32Array(buffer)
32
+ Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_COUNT_INDEX, 0)
33
+ Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_OVERFLOW_INDEX, 0)
34
+
35
+ const oneSecondNs = 1_000_000_000n
36
+ let globalSnapshotSamplingRateWindowStart = 0n
37
+ let snapshotsSampledWithinTheLastSecond = 0
38
+
39
+ ddTraceGlobal[Symbol.for(PROBE_SAMPLER_SYMBOL)] = {
40
+ /**
41
+ * Decide if a probe should be sampled and store sampled probe indexes for the debugger worker.
42
+ *
43
+ * @param {number} probeIndex - The worker-side probe sampling index.
44
+ * @param {string} probeId - The probe id.
45
+ * @param {bigint} nsBetweenSampling - Minimum nanoseconds between samples for this probe.
46
+ * @param {boolean} isSnapshotProducingProbe - Whether this probe counts toward the global snapshot sample limit.
47
+ * @returns {boolean} Whether this probe should make the breakpoint condition pause.
48
+ */
49
+ makeSampleDecision (probeIndex, probeId, nsBetweenSampling, isSnapshotProducingProbe) {
50
+ const now = process.hrtime.bigint()
51
+ const lastCaptureNs = lastCaptureNsByProbeId.get(probeId)
52
+ if (lastCaptureNs !== undefined && now - lastCaptureNs < nsBetweenSampling) return false
53
+
54
+ let shouldResetGlobalSnapshotRateWindow = false
55
+ if (isSnapshotProducingProbe === true) {
56
+ if (now - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
57
+ shouldResetGlobalSnapshotRateWindow = true
58
+ } else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
59
+ return false
60
+ }
61
+ }
62
+
63
+ const sampledProbeCount = Atomics.add(sampledProbeIndexes, SAMPLED_PROBE_COUNT_INDEX, 1)
64
+ if (sampledProbeCount >= MAX_SAMPLED_PROBES_PER_PAUSE) {
65
+ Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_OVERFLOW_INDEX, 1)
66
+ return false
67
+ }
68
+
69
+ if (isSnapshotProducingProbe === true) {
70
+ if (shouldResetGlobalSnapshotRateWindow === true) {
71
+ snapshotsSampledWithinTheLastSecond = 1
72
+ globalSnapshotSamplingRateWindowStart = now
73
+ } else {
74
+ snapshotsSampledWithinTheLastSecond++
75
+ }
76
+ }
77
+
78
+ lastCaptureNsByProbeId.set(probeId, now)
79
+ Atomics.store(sampledProbeIndexes, SAMPLED_PROBE_INDEXES_START + sampledProbeCount, probeIndex)
80
+ return true
81
+ },
82
+
83
+ /**
84
+ * Remove cached sampling state for a probe.
85
+ *
86
+ * @param {string} probeId - The probe id.
87
+ */
88
+ remove (probeId) {
89
+ lastCaptureNsByProbeId.delete(probeId)
90
+ },
91
+ }
92
+
93
+ return buffer
94
+ }
95
+
96
+ /**
97
+ * Remove the runtime sampler from the debuggee context.
98
+ */
99
+ function uninstallProbeSampler () {
100
+ delete ddTraceGlobal[Symbol.for(PROBE_SAMPLER_SYMBOL)]
101
+ }
102
+
103
+ /**
104
+ * Create the shared buffer used to hand sampled probe indexes from breakpoint conditions to the debugger worker.
105
+ *
106
+ * @returns {SharedArrayBuffer}
107
+ */
108
+ function createProbeSamplerBuffer () {
109
+ return new SharedArrayBuffer(
110
+ (SAMPLED_PROBE_INDEXES_START + MAX_SAMPLED_PROBES_PER_PAUSE) * Int32Array.BYTES_PER_ELEMENT
111
+ )
112
+ }
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const DD_TRACE_SYMBOL = 'dd-trace'
4
+ const PROBE_SAMPLER_SYMBOL = 'dd-trace.debugger.probeSampler'
5
+
6
+ // Shared sampler contract used by the main debugger bootstrap and the devtools worker.
7
+ const MAX_SAMPLED_PROBES_PER_PAUSE = 256
8
+ const SAMPLED_PROBE_COUNT_INDEX = 0
9
+ const SAMPLED_PROBE_OVERFLOW_INDEX = 1
10
+ const SAMPLED_PROBE_INDEXES_START = 2
11
+
12
+ module.exports = {
13
+ DD_TRACE_SYMBOL,
14
+ MAX_SAMPLED_PROBES_PER_PAUSE,
15
+ PROBE_SAMPLER_SYMBOL,
16
+ SAMPLED_PROBE_COUNT_INDEX,
17
+ SAMPLED_PROBE_INDEXES_START,
18
+ SAMPLED_PROBE_OVERFLOW_INDEX,
19
+ }
@@ -3,11 +3,14 @@
3
3
  const dgram = require('dgram')
4
4
  const isIP = require('net').isIP
5
5
 
6
+ const { storage } = require('../../datadog-core')
6
7
  const request = require('./exporters/common/request')
7
8
  const log = require('./log')
8
9
  const Histogram = require('./histogram')
9
10
  const { entityId } = require('./exporters/common/docker')
10
11
 
12
+ const legacyStorage = storage('legacy')
13
+
11
14
  const MAX_BUFFER_SIZE = 1024 // limit from the agent
12
15
 
13
16
  const TYPE_COUNTER = 'c'
@@ -98,14 +101,18 @@ class DogStatsDClient {
98
101
  }
99
102
 
100
103
  _sendUdp (queue) {
101
- if (this._family === 0) {
102
- this.#lookup(this._host, (err, address, family) => {
103
- if (err) return log.error('DogStatsDClient: Host not found', err)
104
- this._sendUdpFromQueue(queue, address, family)
105
- })
106
- } else {
107
- this._sendUdpFromQueue(queue, this._host, this._family)
108
- }
104
+ // dgram resolves the local address via the instrumented dns.lookup when it
105
+ // binds on first send; the noop store keeps that self-traffic off the trace.
106
+ legacyStorage.run({ noop: true }, () => {
107
+ if (this._family === 0) {
108
+ this.#lookup(this._host, (error, address, family) => {
109
+ if (error) return log.error('DogStatsDClient: Host not found', error)
110
+ this._sendUdpFromQueue(queue, address, family)
111
+ })
112
+ } else {
113
+ this._sendUdpFromQueue(queue, this._host, this._family)
114
+ }
115
+ })
109
116
  }
110
117
 
111
118
  _sendUdpFromQueue (queue, address, family) {
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
  const { version: ddTraceVersion } = require('../../../../package.json')
3
- const { ITR_CORRELATION_ID } = require('../../src/plugins/util/test')
3
+ const { ITR_CORRELATION_ID, TEST_LEVELS_METADATA } = require('../../src/plugins/util/test')
4
4
  const id = require('../../src/id')
5
5
  const {
6
6
  distributionMetric,
@@ -9,10 +9,15 @@ const {
9
9
  } = require('../ci-visibility/telemetry')
10
10
  const { MsgpackChunk } = require('../msgpack')
11
11
  const { AgentEncoder } = require('./0.4')
12
- const { truncateSpanTestOpt, normalizeSpan } = require('./tags-processors')
12
+ const {
13
+ truncateSpanTestOpt,
14
+ normalizeSpan,
15
+ MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION,
16
+ } = require('./tags-processors')
13
17
 
14
18
  const ENCODING_VERSION = 1
15
19
  const ALLOWED_CONTENT_TYPES = new Set(['test_session_end', 'test_module_end', 'test_suite_end', 'test'])
20
+ const ALLOWED_METADATA_TARGETS = new Set([...ALLOWED_CONTENT_TYPES, TEST_LEVELS_METADATA])
16
21
 
17
22
  const TEST_SUITE_KEYS_LENGTH = 12
18
23
  const TEST_MODULE_KEYS_LENGTH = 11
@@ -36,6 +41,33 @@ function formatSpan (span) {
36
41
  }
37
42
  }
38
43
 
44
+ function isAllowedMetadataTarget (target) {
45
+ return ALLOWED_METADATA_TARGETS.has(target)
46
+ }
47
+
48
+ function isEncodableMetadataValue (value) {
49
+ return typeof value === 'string' || typeof value === 'number'
50
+ }
51
+
52
+ function truncateTestLevelMetadataValue (value) {
53
+ if (typeof value !== 'string' || value.length <= MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION) {
54
+ return value
55
+ }
56
+ return `${value.slice(0, MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION)}...`
57
+ }
58
+
59
+ function truncateTestLevelMetadataTags (tags) {
60
+ const truncatedTags = {}
61
+ let hasTags = false
62
+ for (const key of Object.keys(tags)) {
63
+ const value = truncateTestLevelMetadataValue(tags[key])
64
+ if (!isEncodableMetadataValue(value)) continue
65
+ truncatedTags[key] = value
66
+ hasTags = true
67
+ }
68
+ return hasTags ? truncatedTags : undefined
69
+ }
70
+
39
71
  class AgentlessCiVisibilityEncoder extends AgentEncoder {
40
72
  constructor (writer, { runtimeId, service, env }) {
41
73
  super(writer, INTAKE_SOFT_LIMIT)
@@ -49,6 +81,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
49
81
 
50
82
  this.metadataTags = {}
51
83
  this.wildcardMetadataTags = {}
84
+ this.testLevelsMetadataKeys = []
52
85
 
53
86
  this.reset()
54
87
  }
@@ -60,12 +93,39 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
60
93
  ...tags['*'],
61
94
  }
62
95
  }
63
- for (const type of ALLOWED_CONTENT_TYPES) {
64
- if (tags[type]) {
65
- this.metadataTags[type] = {
66
- ...this.metadataTags[type],
67
- ...tags[type],
68
- }
96
+
97
+ for (const target of Object.keys(tags)) {
98
+ if (target === '*' || !tags[target] || !isAllowedMetadataTarget(target)) continue
99
+
100
+ const targetTags = target === TEST_LEVELS_METADATA
101
+ ? truncateTestLevelMetadataTags(tags[target])
102
+ : tags[target]
103
+ if (!targetTags) continue
104
+
105
+ this.metadataTags[target] = {
106
+ ...this.metadataTags[target],
107
+ ...targetTags,
108
+ }
109
+
110
+ if (target === TEST_LEVELS_METADATA) {
111
+ this.testLevelsMetadataKeys = Object.keys(this.metadataTags[target])
112
+ }
113
+ }
114
+ }
115
+
116
+ _removeDuplicateTestLevelsMetadata (event) {
117
+ if (!ALLOWED_CONTENT_TYPES.has(event.type)) return
118
+
119
+ const meta = event.content.meta
120
+ if (!meta) return
121
+
122
+ const testLevelsMetadataTags = this.metadataTags[TEST_LEVELS_METADATA]
123
+ const testLevelsMetadataKeys = this.testLevelsMetadataKeys
124
+
125
+ for (let i = 0; i < testLevelsMetadataKeys.length; i++) {
126
+ const key = testLevelsMetadataKeys[i]
127
+ if (meta[key] === testLevelsMetadataTags[key]) {
128
+ delete meta[key]
69
129
  }
70
130
  }
71
131
  }
@@ -276,7 +336,11 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
276
336
 
277
337
  this._eventCount += events.length
278
338
 
339
+ const hasTestLevelsMetadata = this.testLevelsMetadataKeys.length !== 0
279
340
  for (const event of events) {
341
+ if (hasTestLevelsMetadata) {
342
+ this._removeDuplicateTestLevelsMetadata(event)
343
+ }
280
344
  this._encodeEvent(bytes, event)
281
345
  }
282
346
  distributionMetric(
@@ -343,24 +407,11 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
343
407
  this._encodeString(bytes, 'version')
344
408
  bytes.writeNumber(payload.version)
345
409
  this._encodeString(bytes, 'metadata')
346
- bytes.writeMapPrefix(Object.keys(payload.metadata).length)
347
- this._encodeString(bytes, '*')
348
- this._encodeMap(bytes, payload.metadata['*'])
349
- if (payload.metadata.test) {
350
- this._encodeString(bytes, 'test')
351
- this._encodeMap(bytes, payload.metadata.test)
352
- }
353
- if (payload.metadata.test_suite_end) {
354
- this._encodeString(bytes, 'test_suite_end')
355
- this._encodeMap(bytes, payload.metadata.test_suite_end)
356
- }
357
- if (payload.metadata.test_module_end) {
358
- this._encodeString(bytes, 'test_module_end')
359
- this._encodeMap(bytes, payload.metadata.test_module_end)
360
- }
361
- if (payload.metadata.test_session_end) {
362
- this._encodeString(bytes, 'test_session_end')
363
- this._encodeMap(bytes, payload.metadata.test_session_end)
410
+ const metadataKeys = Object.keys(payload.metadata)
411
+ bytes.writeMapPrefix(metadataKeys.length)
412
+ for (const metadataKey of metadataKeys) {
413
+ this._encodeString(bytes, metadataKey)
414
+ this._encodeMap(bytes, payload.metadata[metadataKey])
364
415
  }
365
416
  this._encodeString(bytes, 'events')
366
417
  this._eventsOffset = bytes.length
@@ -7,6 +7,7 @@ const log = require('../../log')
7
7
  const { entityId } = require('../common/docker')
8
8
  const tracerVersion = require('../../../../../package.json').version
9
9
  const Writer = require('./writer')
10
+ const { computeIntakeUrl } = require('./intake')
10
11
 
11
12
  /**
12
13
  * Agentless exporter for APM trace intake.
@@ -15,6 +16,7 @@ const Writer = require('./writer')
15
16
  */
16
17
  class AgentlessExporter {
17
18
  #timer
19
+ #config
18
20
 
19
21
  /**
20
22
  * @param {object} config - Configuration object
@@ -24,13 +26,13 @@ class AgentlessExporter {
24
26
  * @param {object} [config.tags] - Tags including runtime-id
25
27
  */
26
28
  constructor (config) {
27
- this._config = config
29
+ this.#config = config
28
30
  const site = config.site ?? 'datadoghq.com'
29
31
 
30
32
  try {
31
- // Agentless traffic carries the Datadog API key, so the intake is always the public https
32
- // endpoint; never derive it from config.url (the agent's cleartext http) or the key leaks.
33
- this._url = new URL(`https://public-trace-http-intake.logs.${site}`)
33
+ // Agentless traffic carries the Datadog API key, so the intake is always an https endpoint
34
+ // derived from the site; never config.url (the agent's cleartext http) or the key leaks.
35
+ this._url = new URL(computeIntakeUrl(site))
34
36
  } catch (err) {
35
37
  log.error('Invalid site for agentless exporter. site=%s. Error: %s', site, err.message)
36
38
  this._url = null
@@ -89,7 +91,7 @@ class AgentlessExporter {
89
91
  export (spans) {
90
92
  this._writer.append(spans)
91
93
 
92
- const { flushInterval } = this._config
94
+ const { flushInterval } = this.#config
93
95
 
94
96
  if (flushInterval === 0) {
95
97
  try {
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ // Per-site hosts for the agentless JSON span intake. Regional data centers serve it from
4
+ // browser-intake-* hosts rather than public-trace-http-intake.logs.<site>, so a single template
5
+ // silently drops spans on us3/us5/ap1/ap2. Mirrors dd-trace-py's AgentlessTraceWriter.INTAKE_URLS
6
+ // (DataDog/dd-trace-py#18514).
7
+ const INTAKE_URLS = {
8
+ 'datadoghq.com': 'https://public-trace-http-intake.logs.datadoghq.com',
9
+ 'datadoghq.eu': 'https://public-trace-http-intake.logs.datadoghq.eu',
10
+ 'us3.datadoghq.com': 'https://trace.browser-intake-us3-datadoghq.com',
11
+ 'us5.datadoghq.com': 'https://trace.browser-intake-us5-datadoghq.com',
12
+ 'ap1.datadoghq.com': 'https://browser-intake-ap1-datadoghq.com',
13
+ 'ap2.datadoghq.com': 'https://browser-intake-ap2-datadoghq.com',
14
+ 'uk1.datadoghq.com': 'https://browser-intake-uk1-datadoghq.com',
15
+ 'datad0g.com': 'https://public-trace-http-intake.logs.datad0g.com',
16
+ }
17
+
18
+ // Path of the JSON span intake on every intake host.
19
+ const INTAKE_PATH = '/api/v2/spans'
20
+
21
+ /**
22
+ * Resolves the agentless intake origin for a Datadog site.
23
+ *
24
+ * Unknown sites fall back to the browser-intake naming: strip the TLD, dash-join the rest, then
25
+ * reattach the TLD, e.g. 'us2.ddog-gov.com' -> 'https://browser-intake-us2-ddog-gov.com'.
26
+ *
27
+ * @param {string} [site] - The Datadog site, e.g. 'us3.datadoghq.com'. Defaults to 'datadoghq.com'.
28
+ * @returns {string} The intake origin, without a path.
29
+ */
30
+ function computeIntakeUrl (site = 'datadoghq.com') {
31
+ const normalized = site.toLowerCase()
32
+ const known = INTAKE_URLS[normalized]
33
+ if (known !== undefined) {
34
+ return known
35
+ }
36
+
37
+ const lastDot = normalized.lastIndexOf('.')
38
+ const prefix = lastDot === -1 ? '' : normalized.slice(0, lastDot)
39
+ const tld = lastDot === -1 ? normalized : normalized.slice(lastDot + 1)
40
+ return `https://browser-intake-${prefix.replaceAll('.', '-')}.${tld}`
41
+ }
42
+
43
+ module.exports = { INTAKE_URLS, INTAKE_PATH, computeIntakeUrl }
@@ -7,6 +7,7 @@ const tracerVersion = require('../../../../../package.json').version
7
7
 
8
8
  const BaseWriter = require('../common/writer')
9
9
  const { AgentlessJSONEncoder } = require('../../encode/agentless-json')
10
+ const { computeIntakeUrl, INTAKE_PATH } = require('./intake')
10
11
 
11
12
  /**
12
13
  * Writer for agentless APM trace intake.
@@ -28,7 +29,7 @@ class AgentlessWriter extends BaseWriter {
28
29
 
29
30
  if (!url) {
30
31
  try {
31
- this._url = new URL(`https://public-trace-http-intake.logs.${site}`)
32
+ this._url = new URL(computeIntakeUrl(site))
32
33
  } catch (err) {
33
34
  log.error(
34
35
  'Invalid site value for agentless intake: %s. Cannot construct URL. Error: %s',
@@ -121,7 +122,7 @@ class AgentlessWriter extends BaseWriter {
121
122
  this.#apiKeyMissing = false
122
123
 
123
124
  const options = {
124
- path: '/v1/input',
125
+ path: INTAKE_PATH,
125
126
  method: 'POST',
126
127
  headers: {
127
128
  'Content-Type': 'application/json',
@@ -140,7 +141,7 @@ class AgentlessWriter extends BaseWriter {
140
141
 
141
142
  request(data, options, (err, res, statusCode) => {
142
143
  if (err) {
143
- this._logRequestError(err, statusCode, count)
144
+ this.#logRequestError(err, statusCode, count)
144
145
  done()
145
146
  return
146
147
  }
@@ -156,7 +157,7 @@ class AgentlessWriter extends BaseWriter {
156
157
  * @param {number} statusCode - HTTP status code (if available)
157
158
  * @param {number} count - Number of traces that were being sent
158
159
  */
159
- _logRequestError (err, statusCode, count) {
160
+ #logRequestError (err, statusCode, count) {
160
161
  if (statusCode === 401 || statusCode === 403) {
161
162
  log.error(
162
163
  'Authentication failed sending %d trace(s) (status %s). Verify DD_API_KEY is valid.',
@@ -11,7 +11,7 @@ const zlib = require('zlib')
11
11
 
12
12
  const { storage } = require('../../../../datadog-core')
13
13
  const log = require('../../log')
14
- const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
14
+ const { parseUrl } = require('./url')
15
15
  const docker = require('./docker')
16
16
  const { httpAgent, httpsAgent } = require('./agents')
17
17
  const {
@@ -27,25 +27,6 @@ const maxActiveBufferSize = 1024 * 1024 * 64
27
27
 
28
28
  let activeBufferSize = 0
29
29
 
30
- /**
31
- * @param {string|URL|object} urlObjOrString
32
- * @returns {object}
33
- */
34
- function parseUrl (urlObjOrString) {
35
- if (urlObjOrString !== null && typeof urlObjOrString === 'object') return urlToHttpOptions(urlObjOrString)
36
-
37
- const url = urlToHttpOptions(new URL(urlObjOrString))
38
-
39
- // Special handling if we're using named pipes on Windows
40
- if (url.protocol === 'unix:' && url.hostname === '.') {
41
- const udsPath = urlObjOrString.slice(5)
42
- url.path = udsPath
43
- url.pathname = udsPath
44
- }
45
-
46
- return url
47
- }
48
-
49
30
  /**
50
31
  * @param {string} hostname Host as resolved by {@link parseUrl}; IPv6 is unbracketed (`::1`).
51
32
  */
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
4
+
5
+ /**
6
+ * Convert an agent/intake URL into Node http(s) request options.
7
+ *
8
+ * A Windows named pipe (`unix://./pipe/<name>`) parses the `.` as the URL
9
+ * authority, dropping it from the path. Fold it back so the socket path stays
10
+ * `//./pipe/<name>`; otherwise it collapses to `/pipe/<name>` and misses the
11
+ * pipe. Exporters receive `config.url` as a URL object, so this must run for
12
+ * both string and object input.
13
+ *
14
+ * @param {string|URL|object} urlObjOrString
15
+ * @returns {object}
16
+ */
17
+ function parseUrl (urlObjOrString) {
18
+ // `urlToHttpOptions` returns `pathname` at runtime, but @types/node narrows it
19
+ // to `ClientRequestArgs`, which omits it; cast so the named-pipe fold below can
20
+ // read and rewrite it.
21
+ const url = /** @type {import('node:http').ClientRequestArgs & { pathname: string }} */ (
22
+ urlObjOrString !== null && typeof urlObjOrString === 'object'
23
+ ? urlToHttpOptions(urlObjOrString)
24
+ : urlToHttpOptions(new URL(urlObjOrString))
25
+ )
26
+
27
+ if (url.protocol === 'unix:' && url.hostname === '.') {
28
+ url.path = url.pathname = `//.${url.pathname}`
29
+ }
30
+
31
+ return url
32
+ }
33
+
34
+ module.exports = { parseUrl }
@@ -37,9 +37,7 @@ function makeRequest (data, url, cb) {
37
37
  'Datadog-Meta-Tracer-Version': pkg.version,
38
38
  'Content-Type': 'application/msgpack',
39
39
  },
40
- protocol: url.protocol,
41
- hostname: url.hostname,
42
- port: url.port,
40
+ url,
43
41
  }
44
42
 
45
43
  log.debug('Request to the intake: %j', options)