dd-trace 6.10.0 → 6.12.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 (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -5,25 +5,17 @@ const pkg = require('../../../../package.json')
5
5
 
6
6
  const { LogCollapsingLowestDenseDDSketch } = require('../../../../vendor/dist/@datadog/sketches-js')
7
7
  const { PATHWAY_HASH, DSM_TRANSACTION_ID, DSM_TRANSACTION_CHECKPOINT } = require('../../../../ext/tags')
8
- const { dsmBase64NameLength } = require('../carrier')
9
8
  const log = require('../log')
10
9
  const processTags = require('../process-tags')
11
10
  const propagationHash = require('../propagation-hash')
12
11
  const { computePathwayHash } = require('./pathway')
13
12
  const { DataStreamsWriter } = require('./writer')
14
- const { getAmqpMessageSize, getHeadersSize, getMessageSize, getSizeOrZero } = require('./size')
13
+ const { getAmqpMessageSize, getHeadersSize, getMessageSize, getSizeOrZero, PATHWAY_FIELD_BYTES } = require('./size')
15
14
  const { SchemaBuilder } = require('./schemas/schema_builder')
16
15
  const { SchemaSampler } = require('./schemas/schema_sampler')
17
16
 
18
17
  const ENTRY_PARENT_HASH = Buffer.from('0000000000000000', 'hex')
19
18
 
20
- // A direction:out checkpoint estimates the size cost of the header the
21
- // producer plugin will inject. The pathway context is always 20 binary
22
- // bytes, encoded as 28 base64 chars; together with the header key and
23
- // JSON framing (matching the prior `JSON.stringify({key: value})` byte
24
- // count minus 1), this is a fixed value.
25
- const PATHWAY_HEADER_BYTES = dsmBase64NameLength + 28 + 6
26
-
27
19
  class StatsPoint {
28
20
  constructor (hash, parentHash, edgeTags) {
29
21
  this.hash = hash.readBigUInt64LE()
@@ -290,10 +282,19 @@ class DataStreamsProcessor {
290
282
  if (this.flushInterval === 0) this.onInterval()
291
283
  }
292
284
 
293
- setCheckpoint (edgeTags, span, ctx, payloadSize = 0) {
285
+ /**
286
+ * @param {string[]} edgeTags Direction tag first.
287
+ * @param {import('../opentracing/span')|null} span
288
+ * @param {object|null|undefined} ctx Parent pathway context.
289
+ * @param {number} [payloadSize] Bytes the caller built, before any propagation context.
290
+ * @param {number} [pathwayContextSize] Bytes the pathway context adds to an outbound payload.
291
+ * Most producers size their payload before this call hands them the context to inject, so they
292
+ * cannot measure it and take the default estimate. Pass `0` to report the payload alone.
293
+ * @returns {object|undefined}
294
+ */
295
+ setCheckpoint (edgeTags, span, ctx, payloadSize = 0, pathwayContextSize = PATHWAY_FIELD_BYTES) {
294
296
  if (!this.enabled) return
295
297
  const nowNs = Date.now() * 1e6
296
- // Callers must place the direction tag at index 0.
297
298
  const direction = edgeTags[0]
298
299
  let pathwayStartNs = nowNs
299
300
  let edgeStartNs = nowNs
@@ -338,14 +339,15 @@ class DataStreamsProcessor {
338
339
  const pathwayLatencyNs = nowNs - pathwayStartNs
339
340
  const dataStreamsContext = {
340
341
  hash,
341
- edgeStartNs,
342
+ // start the next hop's edge clock here so edge latency stays per-hop
343
+ edgeStartNs: nowNs,
342
344
  pathwayStartNs,
343
345
  previousDirection: direction,
344
346
  closestOppositeDirectionHash,
345
347
  closestOppositeDirectionEdgeStart,
346
348
  }
347
349
  if (direction === 'direction:out') {
348
- payloadSize += PATHWAY_HEADER_BYTES
350
+ payloadSize += pathwayContextSize
349
351
  }
350
352
  const checkpoint = {
351
353
  currentTimestamp: nowNs,
@@ -2,6 +2,10 @@
2
2
 
3
3
  const { types } = require('util')
4
4
 
5
+ // Byte cost of the `,"dd-pathway-ctx-base64":"<28 chars>"` field a producer adds to a JSON carrier.
6
+ // A literal so eager `size` consumers don't pull in `./pathway`; `pathway.spec.js` pins it there.
7
+ const PATHWAY_FIELD_BYTES = 55
8
+
5
9
  function getSizeOrZero (obj) {
6
10
  if (typeof obj === 'string') {
7
11
  return Buffer.byteLength(obj, 'utf8')
@@ -54,4 +58,5 @@ module.exports = {
54
58
  getHeadersSize,
55
59
  getSizeOrZero,
56
60
  getAmqpMessageSize,
61
+ PATHWAY_FIELD_BYTES,
57
62
  }
@@ -4,4 +4,5 @@ module.exports = {
4
4
  DEBUGGER_DIAGNOSTICS_V1: '/debugger/v1/diagnostics',
5
5
  DEBUGGER_INPUT_V1: '/debugger/v1/input',
6
6
  DEBUGGER_INPUT_V2: '/debugger/v2/input',
7
+ INSPECT_SEGMENT_GLOBAL_PROPERTY: 'debuggerInspectSegment',
7
8
  }
@@ -55,7 +55,7 @@ function compileSegments (segments) {
55
55
  ? `(() => {
56
56
  try {
57
57
  const result = ${compile(json)}
58
- return typeof result === 'string' ? result : $dd_inspect(result, $dd_segmentInspectOptions)
58
+ return typeof result === 'string' ? result : $dd_inspectSegment(result)
59
59
  } catch (e) {
60
60
  return { expr: ${JSON.stringify(dsl)}, message: \`\${e.name}: \${e.message}\` }
61
61
  }
@@ -4,6 +4,7 @@ const { randomUUID } = require('crypto')
4
4
  const { workerData: { probeSamplerBuffer } } = require('worker_threads')
5
5
  const { version } = require('../../../../../package.json')
6
6
  const processTags = require('../../process-tags')
7
+ const { INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('../constants')
7
8
  const {
8
9
  MAX_SAMPLED_PROBES_PER_PAUSE,
9
10
  SAMPLED_PROBE_COUNT_INDEX,
@@ -23,19 +24,12 @@ require('./remote_config')
23
24
 
24
25
  /** @typedef {import('node:inspector').Debugger.EvaluateOnCallFrameReturnType} EvaluateOnCallFrameResult */
25
26
 
27
+ const templateExpressionSetupCode = 'const $dd_inspectSegment = ' +
28
+ `globalThis[Symbol.for('dd-trace')][${JSON.stringify(INSPECT_SEGMENT_GLOBAL_PROPERTY)}];`
29
+
26
30
  // Expression to run on a call frame of the paused thread to get its active trace and span id.
27
- const templateExpressionSetupCode = `
28
- const $dd_inspect = global.require('node:util').inspect;
29
- const $dd_segmentInspectOptions = {
30
- depth: 0,
31
- customInspect: false,
32
- maxArrayLength: 3,
33
- maxStringLength: 8 * 1024,
34
- breakLength: Infinity
35
- };
36
- `
37
31
  const getDDTagsExpression = `(() => {
38
- const context = global.require('dd-trace').scope().active()?.context();
32
+ const context = globalThis._ddtrace.scope().active()?.context();
39
33
  return { trace_id: context?.toTraceId(), span_id: context?.toSpanId() }
40
34
  })()`
41
35
 
@@ -7,7 +7,7 @@ const { Worker, MessageChannel, threadId: parentThreadId } = require('worker_thr
7
7
  const log = require('../log')
8
8
  const { fetchAgentInfo } = require('../agent/info')
9
9
  const getDebuggerConfig = require('./config')
10
- const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2 } = require('./constants')
10
+ const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2, INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('./constants')
11
11
  const { installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
12
12
 
13
13
  /**
@@ -64,7 +64,9 @@ function start (config, rcInstance) {
64
64
  const logChannel = new MessageChannel()
65
65
  configChannel = new MessageChannel()
66
66
 
67
- globalThis[Symbol.for('dd-trace')].utilTypes = types
67
+ const debuggerGlobals = globalThis[Symbol.for('dd-trace')]
68
+ debuggerGlobals.utilTypes = types
69
+ debuggerGlobals[INSPECT_SEGMENT_GLOBAL_PROPERTY] = require('./inspect-segment')
68
70
 
69
71
  const probeSamplerBuffer = installProbeSampler()
70
72
 
@@ -0,0 +1,154 @@
1
+ 'use strict'
2
+
3
+ const { inspect, types } = require('node:util')
4
+
5
+ const { NODE_MAJOR } = require('../../../../version')
6
+
7
+ /** @typedef {NonNullable<ReturnType<typeof globalThis.Object.getOwnPropertyDescriptor>>} PropertyDescriptor */
8
+ /** @typedef {Map<unknown, unknown> | Set<unknown>} Collection */
9
+
10
+ const mapEntries = Map.prototype.entries
11
+ const mapSet = Map.prototype.set
12
+ const mapSizeGetter = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get
13
+ const setAdd = Set.prototype.add
14
+ const setSizeGetter = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get
15
+ const setValues = Set.prototype.values
16
+ const mapIteratorNext = Object.getPrototypeOf(mapEntries.call(new Map())).next
17
+ const setIteratorNext = Object.getPrototypeOf(setValues.call(new Set())).next
18
+
19
+ const maxCollectionEntries = 3
20
+ const maxProperties = 5
21
+ const segmentInspectOptions = {
22
+ depth: 0,
23
+ customInspect: false,
24
+ maxArrayLength: maxCollectionEntries,
25
+ maxStringLength: 8 * 1024,
26
+ breakLength: Infinity,
27
+ }
28
+
29
+ module.exports = inspectSegment
30
+
31
+ /**
32
+ * Inspect a dynamic-instrumentation template value without invoking user code.
33
+ * Unlike collections, `util.inspect` has no option for limiting the number of object properties, so this function
34
+ * truncates objects before inspecting them.
35
+ *
36
+ * @param {unknown} value
37
+ * @returns {string}
38
+ */
39
+ function inspectSegment (value) {
40
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
41
+ return inspect(value, segmentInspectOptions)
42
+ }
43
+ if (types.isProxy(value)) return '[Proxy]'
44
+ if (types.isMap(value)) return inspectCollection(value, true)
45
+ if (types.isSet(value)) return inspectCollection(value, false)
46
+ if (
47
+ Array.isArray(value) ||
48
+ types.isTypedArray(value) ||
49
+ types.isAnyArrayBuffer(value) ||
50
+ types.isDataView(value) ||
51
+ types.isWeakMap(value) ||
52
+ types.isWeakSet(value) ||
53
+ types.isMapIterator(value) ||
54
+ types.isSetIterator(value)
55
+ ) {
56
+ return inspect(value, segmentInspectOptions)
57
+ }
58
+
59
+ /** @type {(string | symbol)[]} */
60
+ const keys = Object.keys(value)
61
+ let propertyCount = keys.length
62
+ const symbols = Object.getOwnPropertySymbols(value)
63
+ for (let i = 0; i < symbols.length; i++) {
64
+ if (Object.getOwnPropertyDescriptor(value, symbols[i])?.enumerable === true) {
65
+ propertyCount++
66
+ if (keys.length < maxProperties) keys.push(symbols[i])
67
+ }
68
+ }
69
+
70
+ if (propertyCount <= maxProperties) {
71
+ // TODO: Decide whether allowing util.inspect to invoke Symbol.toStringTag getters is acceptable. If it is,
72
+ // remove inspectionCanRunUserCode and the related omission paths.
73
+ if (inspectionCanRunUserCode(value)) {
74
+ return '[Value omitted: inspection may execute user code]'
75
+ }
76
+ return inspect(value, segmentInspectOptions)
77
+ }
78
+
79
+ const truncated = {}
80
+ for (let i = 0; i < maxProperties; i++) {
81
+ const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(value, keys[i]))
82
+ if (
83
+ (keys[i] === Symbol.toStringTag && descriptor.get !== undefined) ||
84
+ (descriptor.value !== value && inspectionCanRunUserCode(descriptor.value))
85
+ ) {
86
+ return '[Value omitted: inspection may execute user code]'
87
+ }
88
+ if (descriptor.value === value) descriptor.value = truncated
89
+ Object.defineProperty(truncated, keys[i], descriptor)
90
+ }
91
+
92
+ const omitted = propertyCount - maxProperties
93
+ const inspected = inspect(truncated, segmentInspectOptions)
94
+ return `${inspected.slice(0, -2)}, ... ${omitted} more ${omitted === 1 ? 'property' : 'properties'} }`
95
+ }
96
+
97
+ /**
98
+ * Inspect a Map or Set while bounding the number of entries on Node.js 18, where `util.inspect` does not.
99
+ *
100
+ * @param {Collection} value
101
+ * @param {boolean} isMap
102
+ * @returns {string}
103
+ */
104
+ function inspectCollection (value, isMap) {
105
+ if (NODE_MAJOR !== 18) return inspect(value, segmentInspectOptions)
106
+
107
+ const size = (isMap ? mapSizeGetter : setSizeGetter).call(value)
108
+ if (size <= maxCollectionEntries) return inspect(value, segmentInspectOptions)
109
+
110
+ const truncated = isMap ? new Map() : new Set()
111
+ const iterator = (isMap ? mapEntries : setValues).call(value)
112
+ const iteratorNext = isMap ? mapIteratorNext : setIteratorNext
113
+
114
+ for (let i = 0; i < maxCollectionEntries; i++) {
115
+ const result = iteratorNext.call(iterator)
116
+ if (result.done) break
117
+
118
+ if (isMap) {
119
+ const entry = result.value
120
+ const key = entry[0] === value ? truncated : entry[0]
121
+ const entryValue = entry[1] === value ? truncated : entry[1]
122
+ mapSet.call(truncated, key, entryValue)
123
+ } else {
124
+ const entryValue = result.value === value ? truncated : result.value
125
+ setAdd.call(truncated, entryValue)
126
+ }
127
+ }
128
+
129
+ const type = isMap ? 'Map' : 'Set'
130
+ const inspected = inspect(truncated, segmentInspectOptions)
131
+ const normalized = inspected.replace(`${type}(${maxCollectionEntries})`, `${type}(${size})`)
132
+ const remaining = size - maxCollectionEntries
133
+ return `${normalized.slice(0, -2)}, ... ${remaining} more item${remaining === 1 ? '' : 's'} }`
134
+ }
135
+
136
+ /**
137
+ * Determine whether inspecting a value could invoke a proxy trap or toStringTag getter.
138
+ *
139
+ * @param {unknown} value
140
+ * @returns {boolean}
141
+ */
142
+ function inspectionCanRunUserCode (value) {
143
+ const type = typeof value
144
+ if (value === null || (type !== 'object' && type !== 'function')) return false
145
+ if (types.isProxy(value)) return true
146
+
147
+ let current = value
148
+ while (current !== null) {
149
+ if (Object.getOwnPropertyDescriptor(current, Symbol.toStringTag)?.get !== undefined) return true
150
+ current = Object.getPrototypeOf(current)
151
+ if (types.isProxy(current)) return true
152
+ }
153
+ return false
154
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ EVP_PROXY_PATH_V2: '/evp_proxy/v2',
5
+ EVP_PROXY_PATH_V4: '/evp_proxy/v4',
6
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
7
+ EVP_EVENT_PLATFORM_SUBDOMAIN: 'event-platform-intake',
8
+ }
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+
3
+ const { format } = require('node:url')
4
+
5
+ const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
6
+ const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
7
+ const log = require('../log')
8
+
9
+ /**
10
+ * @typedef {object} DirectEVPRoute
11
+ * @property {URL} url - Direct intake URL
12
+ * @property {string} basePath - Direct intake base path
13
+ * @property {object} headers - Direct intake authentication headers
14
+ * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
15
+ */
16
+
17
+ /**
18
+ * Creates an authenticated direct EVP intake route.
19
+ *
20
+ * This helper does not perform local receiver discovery.
21
+ *
22
+ * @param {import('../config/config-base')} config - Tracer configuration
23
+ * @param {string} intake - EVP intake subdomain
24
+ * @returns {DirectEVPRoute|undefined} Direct route when credentials and site are available
25
+ */
26
+ function createDirectEVPRoute (config, intake) {
27
+ const apiKey = config.DD_API_KEY
28
+ if (!apiKey || !config.site) return
29
+
30
+ try {
31
+ const hostname = `${intake}.${config.site}`.toLowerCase()
32
+ const url = new URL(format({
33
+ protocol: 'https:',
34
+ hostname,
35
+ }))
36
+ if (
37
+ url.hostname !== hostname ||
38
+ url.username ||
39
+ url.password ||
40
+ url.port ||
41
+ url.pathname !== '/' ||
42
+ url.search ||
43
+ url.hash
44
+ ) {
45
+ throw new Error('Invalid direct EVP intake URL')
46
+ }
47
+
48
+ const proxyUrl = getProxyForUrl(url.href)
49
+ const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined
50
+
51
+ return {
52
+ url,
53
+ basePath: '',
54
+ headers: {
55
+ 'DD-API-KEY': apiKey,
56
+ },
57
+ ...(agent && { agent }),
58
+ }
59
+ } catch (error) {
60
+ log.debug('Unable to configure direct EVP intake: %s', error.message)
61
+ }
62
+ }
63
+
64
+ module.exports = { createDirectEVPRoute }
@@ -0,0 +1,133 @@
1
+ 'use strict'
2
+
3
+ const { fetchAgentInfo } = require('../agent/info')
4
+ const log = require('../log')
5
+
6
+ const TRAILING_SLASHES = /\/+$/
7
+
8
+ /**
9
+ * Receiver discovery contract
10
+ *
11
+ * The tracer sends `GET /info` to its configured local Agent URL. An
12
+ * Agent-compatible trace receiver produces the response. The tracer does not.
13
+ *
14
+ * The full Agent and serverless-init embed the same trace receiver. A future
15
+ * in-process receiver can expose the same contract. Callers can therefore
16
+ * select capabilities without detecting the receiver implementation.
17
+ *
18
+ * For EVP discovery, `endpoints` advertises registered proxy paths.
19
+ * `evp_proxy_allowed_headers` advertises headers that the proxy forwards to
20
+ * intake. It does not include routing headers that the proxy consumes, such as
21
+ * `X-Datadog-EVP-Subdomain`.
22
+ *
23
+ * An advertised route is not a health check. The receiver can register an EVP
24
+ * route while configuration disables its handler. The request then returns
25
+ * `405`. The `/info` version also identifies the embedded Agent code, not a
26
+ * serverless-init image or deployment type.
27
+ *
28
+ * This module only discovers a candidate route. A missing or unresponsive
29
+ * `/info` endpoint returns an error through the shared request timeout and
30
+ * retry policy. A valid response without a compatible path returns no route.
31
+ * Discovery sends no events, so the caller can safely select direct intake
32
+ * after either result. The caller also owns later delivery failures. Exposure
33
+ * delivery uses retries and can therefore produce duplicates. After local
34
+ * retries fail, the caller can retry through direct intake and use that route
35
+ * for future batches.
36
+ *
37
+ * Reference implementations:
38
+ *
39
+ * Agent `/info` and EVP proxy:
40
+ * https://github.com/DataDog/datadog-agent/tree/main/pkg/trace/api
41
+ *
42
+ * serverless-init entry point and embedded trace receiver:
43
+ * https://github.com/DataDog/datadog-agent/blob/main/cmd/serverless-init/main.go
44
+ * https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/trace/trace.go
45
+ */
46
+
47
+ /**
48
+ * Selects the first advertised EVP proxy path that the caller supports.
49
+ *
50
+ * @param {object} agentInfo - Agent `/info` response
51
+ * @param {object} options - Selection options
52
+ * @param {string[]} options.supportedPaths - Supported paths in preference order
53
+ * @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
54
+ * header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
55
+ * @returns {string|undefined} Selected normalized path
56
+ */
57
+ function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] } = {}) {
58
+ if (!Array.isArray(agentInfo?.endpoints) ||
59
+ !Array.isArray(supportedPaths) ||
60
+ !Array.isArray(requiredHeaders) ||
61
+ requiredHeaders.some(header => typeof header !== 'string')) {
62
+ return
63
+ }
64
+
65
+ const allowedHeaders = agentInfo.evp_proxy_allowed_headers
66
+ if (allowedHeaders !== undefined) {
67
+ if (!Array.isArray(allowedHeaders)) return
68
+
69
+ const normalizedHeaders = new Set()
70
+ for (const header of allowedHeaders) {
71
+ if (typeof header === 'string') {
72
+ normalizedHeaders.add(header.toLowerCase())
73
+ }
74
+ }
75
+
76
+ if (requiredHeaders.some(header => !normalizedHeaders.has(header.toLowerCase()))) {
77
+ return
78
+ }
79
+ }
80
+
81
+ const advertisedPaths = new Set()
82
+ for (const endpoint of agentInfo.endpoints) {
83
+ if (typeof endpoint === 'string') {
84
+ advertisedPaths.add(endpoint.replace(TRAILING_SLASHES, ''))
85
+ }
86
+ }
87
+
88
+ for (const supportedPath of supportedPaths) {
89
+ if (typeof supportedPath !== 'string') continue
90
+
91
+ const normalizedPath = supportedPath.replace(TRAILING_SLASHES, '')
92
+ if (advertisedPaths.has(normalizedPath)) {
93
+ return normalizedPath
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Discovers an EVP proxy route through the configured Agent URL.
100
+ *
101
+ * This function performs discovery only when the caller invokes it. It stores
102
+ * no state. The Agent information client owns its existing response cache.
103
+ *
104
+ * @param {URL} url - Configured Agent URL
105
+ * @param {object} options - Selection options
106
+ * @param {string[]} options.supportedPaths - Supported paths in preference order
107
+ * @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
108
+ * header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
109
+ * @param {(error: Error|null, route?: {url: URL, basePath: string}) => void} callback - Result callback
110
+ * @returns {void}
111
+ */
112
+ function discoverEVPProxy (url, options, callback) {
113
+ fetchAgentInfo(url, (error, agentInfo) => {
114
+ if (error) {
115
+ callback(error)
116
+ return
117
+ }
118
+
119
+ const basePath = selectEVPProxyPath(agentInfo, options)
120
+ if (basePath === undefined) {
121
+ callback(null)
122
+ return
123
+ }
124
+
125
+ log.debug('EVP proxy route %s discovered through the configured local receiver', basePath)
126
+ callback(null, { url, basePath })
127
+ })
128
+ }
129
+
130
+ module.exports = {
131
+ discoverEVPProxy,
132
+ selectEVPProxyPath,
133
+ }
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const LEADING_SLASHES = /^\/+/
4
+ const TRAILING_SLASHES = /\/+$/
5
+
6
+ /**
7
+ * Joins a caller-supplied EVP proxy path and product endpoint.
8
+ *
9
+ * This utility does not perform EVP proxy discovery.
10
+ *
11
+ * @param {string} basePath - EVP proxy base path
12
+ * @param {string} endpoint - Product intake endpoint
13
+ * @returns {string} Joined request path
14
+ */
15
+ function joinEVPProxyPath (basePath, endpoint) {
16
+ const normalizedBasePath = basePath.replace(TRAILING_SLASHES, '')
17
+ const normalizedEndpoint = endpoint.replace(LEADING_SLASHES, '')
18
+
19
+ return `${normalizedBasePath}/${normalizedEndpoint}`
20
+ }
21
+
22
+ module.exports = { joinEVPProxyPath }
@@ -3,7 +3,7 @@
3
3
  const { inspect } = require('node:util')
4
4
  const { channel } = require('dc-polyfill')
5
5
 
6
- const request = require('../common/request')
6
+ const commonRequest = require('../common/request')
7
7
  const { logIntegrations, logAgentError } = require('../../startup-log')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const log = require('../../log')
@@ -15,12 +15,15 @@ const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
15
15
  const firstFlushChannel = channel('dd-trace:exporter:first-flush')
16
16
 
17
17
  class AgentWriter extends BaseWriter {
18
+ #request = commonRequest
19
+ #requestTracker
20
+
18
21
  constructor (...args) {
19
22
  super({
20
23
  ...args[0],
21
24
  beforeFirstFlush: () => firstFlushChannel.publish(),
22
25
  })
23
- const { prioritySampler, lookup, protocolVersion, headers } = args[0]
26
+ const { prioritySampler, lookup, protocolVersion, headers, isTestOptimization } = args[0]
24
27
  const AgentEncoder = getEncoder(protocolVersion)
25
28
 
26
29
  this._prioritySampler = prioritySampler
@@ -28,13 +31,33 @@ class AgentWriter extends BaseWriter {
28
31
  this._protocolVersion = protocolVersion
29
32
  this._headers = headers
30
33
  this._encoder = new AgentEncoder(this)
34
+ if (isTestOptimization) {
35
+ this.#request = require('../../ci-visibility/exporters/request')
36
+ const TestOptimizationRequestTracker = require('../../ci-visibility/exporters/agentless/request-tracker')
37
+ this.#requestTracker = new TestOptimizationRequestTracker(this)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Flushes payloads, including requests already in flight during Test Optimization finalization.
43
+ *
44
+ * @param {(error?: Error) => void} [done]
45
+ * @param {{ deadline?: number }} [options]
46
+ * @returns {void}
47
+ */
48
+ flush (done, options) {
49
+ if (this.#requestTracker) {
50
+ this.#requestTracker.flush(done, options)
51
+ return
52
+ }
53
+ super.flush(done, options)
31
54
  }
32
55
 
33
- _sendPayload (data, count, done) {
56
+ _sendPayload (data, count, done, flushOptions) {
34
57
  runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
35
58
 
36
59
  const { _headers, _lookup, _protocolVersion, _url } = this
37
- makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, (err, res, status, headers) => {
60
+ const onResponse = (err, res, status, headers) => {
38
61
  if (status) {
39
62
  runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
40
63
  runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
@@ -49,7 +72,7 @@ class AgentWriter extends BaseWriter {
49
72
 
50
73
  if (err) {
51
74
  log.errorWithoutTelemetry('Error sending payload to the agent (status code: %s)', err.status, err)
52
- done()
75
+ done(flushOptions?.deadline === undefined ? undefined : err)
53
76
  return
54
77
  }
55
78
 
@@ -74,7 +97,19 @@ class AgentWriter extends BaseWriter {
74
97
  runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
75
98
  }
76
99
  done()
77
- })
100
+ }
101
+ makeRequest(
102
+ _protocolVersion,
103
+ data,
104
+ count,
105
+ _url,
106
+ _headers,
107
+ _lookup,
108
+ flushOptions,
109
+ this.#request,
110
+ this.#requestTracker,
111
+ onResponse
112
+ )
78
113
  }
79
114
  }
80
115
 
@@ -84,7 +119,7 @@ function getEncoder (protocolVersion) {
84
119
  : require('../../encode/0.4').AgentEncoder
85
120
  }
86
121
 
87
- function makeRequest (version, data, count, url, headers, lookup, cb) {
122
+ function makeRequest (version, data, count, url, headers, lookup, flushOptions, request, requestTracker, cb) {
88
123
  const options = {
89
124
  path: `/v${version}/traces`,
90
125
  method: 'PUT',
@@ -100,16 +135,21 @@ function makeRequest (version, data, count, url, headers, lookup, cb) {
100
135
  lookup,
101
136
  url,
102
137
  }
138
+ if (flushOptions?.deadline !== undefined) {
139
+ options.deadline = flushOptions.deadline
140
+ }
103
141
 
104
142
  log.debug('Request to the agent: %j', options)
105
143
 
106
- request(data, options, (err, res, status, headers) => {
144
+ const onResponse = (err, res, status, headers) => {
107
145
  logIntegrations()
108
146
  if (status !== 404 && status !== 200 && err) {
109
147
  logAgentError({ status, message: err.message ?? inspect(err) })
110
148
  }
111
149
  cb(err, res, status, headers)
112
- })
150
+ }
151
+ if (requestTracker) requestTracker.send(request, data, options, onResponse)
152
+ else request(data, options, onResponse)
113
153
  }
114
154
 
115
155
  module.exports = AgentWriter