dd-trace 6.12.0 → 6.14.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 (251) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -2,49 +2,186 @@
2
2
 
3
3
  const dgram = require('dgram')
4
4
  const isIP = require('net').isIP
5
+ const { performance } = require('node:perf_hooks')
5
6
 
7
+ const { channel } = require('dc-polyfill')
8
+
9
+ const tracerVersion = require('../../../package.json').version
6
10
  const { storage } = require('../../datadog-core')
7
11
  const request = require('./exporters/common/request')
8
12
  const log = require('./log')
9
13
  const Histogram = require('./histogram')
10
14
  const { entityId } = require('./exporters/common/docker')
15
+ const { registerTelemetryFlusher } = require('./flush')
16
+ const { createServerlessDeliveryTracker } = require('./serverless')
11
17
 
12
18
  const legacyStorage = storage('legacy')
13
19
 
14
20
  const MAX_BUFFER_SIZE = 1024 // limit from the agent
21
+ const TELEMETRY_INTERVAL = 10_000
15
22
 
16
23
  const TYPE_COUNTER = 'c'
17
24
  const TYPE_GAUGE = 'g'
18
25
  const TYPE_DISTRIBUTION = 'd'
19
26
  const TYPE_HISTOGRAM = 'h'
20
27
 
28
+ const TYPE_COUNTER_INDEX = 0
29
+ const TYPE_GAUGE_INDEX = 1
30
+ const TYPE_DISTRIBUTION_INDEX = 2
31
+ const TYPE_HISTOGRAM_INDEX = 3
32
+ const TYPE_LABELS = ['count', 'gauge', 'distribution', 'histogram']
33
+
34
+ /**
35
+ * @typedef {'c'|'g'|'d'|'h'} MetricType
36
+ */
37
+
38
+ /**
39
+ * @typedef {object} DogStatsDBufferState
40
+ * @property {string} message
41
+ * @property {number} offset
42
+ * @property {Buffer[]} queue
43
+ */
44
+
45
+ /**
46
+ * @typedef {object} DogStatsDClientOptions
47
+ * @property {string} host
48
+ * @property {number} port
49
+ * @property {string[]} tags
50
+ * @property {typeof import('node:dns').lookup} lookup
51
+ * @property {URL|string} [metricsProxyUrl]
52
+ */
53
+
54
+ /**
55
+ * @typedef {object} DogStatsDTelemetryState
56
+ * @property {number[]} aggregatedContextsByType
57
+ * @property {number} bytesDropped
58
+ * @property {number} bytesSent
59
+ * @property {number[]} metricsByType
60
+ * @property {number} nextFlush
61
+ * @property {number} packetsDropped
62
+ * @property {number} packetsSent
63
+ * @property {DogStatsDBufferState} payload
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} MetricNode
68
+ * @property {Map<string, MetricNode>} nodes
69
+ * @property {number} submissions
70
+ * @property {number|Histogram|null} value
71
+ */
72
+
73
+ /**
74
+ * @callback CaptureMetric
75
+ * @param {MetricNode} node
76
+ * @param {string} name
77
+ * @param {string[]} tags
78
+ * @returns {void}
79
+ */
80
+
81
+ const identityRefreshChannel = channel('datadog:identity:refresh')
82
+
21
83
  /**
22
84
  * @import { DogStatsD } from "../../../index.d.ts"
23
85
  * @implements {DogStatsD}
24
86
  */
25
87
  class DogStatsDClient {
88
+ #family
89
+ #host
90
+ #httpOptions
26
91
  #lookup
92
+ #metrics = { message: '', offset: 0, queue: [] }
93
+ #port
94
+ #serverlessDeliveryTracker
27
95
  #tagsPrefix
28
- constructor (options) {
96
+ #telemetryHttpTagsPrefix
97
+ #telemetryUdpTagsPrefix
98
+ #udp4
99
+ #udp6
100
+
101
+ /** @type {DogStatsDTelemetryState|undefined} */
102
+ telemetry
103
+
104
+ /**
105
+ * @param {DogStatsDClientOptions} options - DogStatsD transport options
106
+ * @param {boolean} [telemetryEnabled] - Whether to collect client telemetry
107
+ */
108
+ constructor (options, telemetryEnabled = false) {
29
109
  this.#lookup = options.lookup
30
110
  if (options.metricsProxyUrl) {
31
- this._httpOptions = {
111
+ this.#httpOptions = {
32
112
  method: 'POST',
113
+ retry: false,
33
114
  url: options.metricsProxyUrl.toString(),
34
115
  path: '/dogstatsd/v2/proxy',
35
116
  }
36
117
  }
37
118
 
38
- this._host = options.host
39
- this._family = isIP(this._host)
40
- this._port = options.port
41
- this._tags = options.tags
42
- this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
43
- this._queue = []
44
- this._buffer = ''
45
- this._offset = 0
46
- this._udp4 = this._socket('udp4')
47
- this._udp6 = this._socket('udp6')
119
+ this.#family = isIP(options.host)
120
+ this.#host = options.host
121
+ this.#port = options.port
122
+ this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
123
+
124
+ if (telemetryEnabled) {
125
+ this.telemetry = {
126
+ aggregatedContextsByType: [0, 0, 0, 0],
127
+ bytesDropped: 0,
128
+ bytesSent: 0,
129
+ metricsByType: [0, 0, 0, 0],
130
+ nextFlush: performance.now() + TELEMETRY_INTERVAL,
131
+ packetsDropped: 0,
132
+ packetsSent: 0,
133
+ payload: { message: '', offset: 0, queue: [] },
134
+ }
135
+ }
136
+
137
+ this.#updateTagPrefixes(options.tags.length ? `|#${options.tags.join(',')}` : '')
138
+
139
+ this.#udp4 = this._socket('udp4')
140
+ this.#udp6 = this._socket('udp6')
141
+ }
142
+
143
+ /**
144
+ * Recomputes the cached tags and tag-prefix (mirrors the constructor) after a `config.tags`
145
+ * change, e.g. a MicroVM clone resume.
146
+ *
147
+ * Buffered lines have the old prefix baked in, and on a clone resume they were produced during
148
+ * the image build, so every clone holds the same bytes — flushing them would submit one identical
149
+ * copy per clone. Dropping is right here for that reason only: for a tag change on a live process
150
+ * the buffer holds unique data whose old tags are still correct, so that case wants a flush
151
+ * before the swap.
152
+ *
153
+ * @param {string[]} tags - DogStatsD-formatted tags (e.g. `['key:value']`)
154
+ */
155
+ updateTags (tags) {
156
+ const tagsPrefix = tags.length ? `|#${tags.join(',')}` : ''
157
+ if (tagsPrefix === this.#tagsPrefix) return
158
+
159
+ this.#updateTagPrefixes(tagsPrefix)
160
+ this.#metrics.queue = []
161
+ this.#metrics.message = ''
162
+ this.#metrics.offset = 0
163
+
164
+ const payload = this.telemetry?.payload
165
+ if (payload) {
166
+ payload.queue = []
167
+ payload.message = ''
168
+ payload.offset = 0
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Updates the prefixes used for application metrics and client telemetry.
174
+ * @param {string} tagsPrefix - Serialized global DogStatsD tags
175
+ */
176
+ #updateTagPrefixes (tagsPrefix) {
177
+ this.#tagsPrefix = tagsPrefix
178
+
179
+ if (!this.telemetry) return
180
+
181
+ const separator = tagsPrefix ? ',' : '|#'
182
+ const prefix = `${tagsPrefix}${separator}client:nodejs,client_version:${tracerVersion},client_transport:`
183
+ this.#telemetryHttpTagsPrefix = `${prefix}http`
184
+ this.#telemetryUdpTagsPrefix = `${prefix}udp`
48
185
  }
49
186
 
50
187
  increment (stat, value, tags) {
@@ -67,100 +204,325 @@ class DogStatsDClient {
67
204
  this._add(stat, value, TYPE_HISTOGRAM, tags)
68
205
  }
69
206
 
70
- flush () {
71
- const queue = this._enqueue()
207
+ /**
208
+ * @param {boolean|(() => void)} [forceTelemetry] - Whether to ignore the telemetry interval, or completion callback
209
+ * @param {() => void} [done] - Called after serverless deliveries complete
210
+ * @returns {void}
211
+ */
212
+ flush (forceTelemetry = false, done) {
213
+ if (typeof forceTelemetry === 'function') {
214
+ done = forceTelemetry
215
+ forceTelemetry = false
216
+ }
217
+
218
+ let complete
219
+ let track
220
+ if (done && !this.#serverlessDeliveryTracker) {
221
+ let pending = 1
222
+ complete = () => {
223
+ if (--pending === 0) done()
224
+ }
225
+ track = send => {
226
+ pending++
227
+ send(complete)
228
+ }
229
+ }
230
+
231
+ this.#flush(this.#metrics, true, track)
232
+ if (this.telemetry) this.#flushTelemetry(forceTelemetry, track)
233
+
234
+ if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
235
+ complete?.()
236
+ }
237
+
238
+ /**
239
+ * @param {boolean} force - Whether to ignore the telemetry interval
240
+ * @param {((send: (done?: () => void) => void) => void)} [track] - Tracks a non-serverless delivery
241
+ * @returns {void}
242
+ */
243
+ #flushTelemetry (force, track) {
244
+ const telemetry = this.telemetry
245
+
246
+ const now = performance.now()
247
+
248
+ if (!force && now < telemetry.nextFlush) return
249
+
250
+ telemetry.nextFlush = now + TELEMETRY_INTERVAL
251
+
252
+ let aggregatedContexts = 0
253
+ let metrics = 0
254
+ for (let index = 0; index < TYPE_LABELS.length; index++) {
255
+ aggregatedContexts += telemetry.aggregatedContextsByType[index]
256
+ metrics += telemetry.metricsByType[index]
257
+ }
258
+
259
+ this.#addTelemetry('datadog.dogstatsd.client.metrics', metrics)
260
+ this.#addTelemetry('datadog.dogstatsd.client.aggregated_context', aggregatedContexts)
261
+ for (let index = 0; index < TYPE_LABELS.length; index++) {
262
+ this.#addTelemetry(
263
+ 'datadog.dogstatsd.client.metrics_by_type',
264
+ telemetry.metricsByType[index],
265
+ TYPE_LABELS[index]
266
+ )
267
+ this.#addTelemetry(
268
+ 'datadog.dogstatsd.client.aggregated_context_by_type',
269
+ telemetry.aggregatedContextsByType[index],
270
+ TYPE_LABELS[index]
271
+ )
272
+ }
273
+ this.#addTelemetry('datadog.dogstatsd.client.bytes_sent', telemetry.bytesSent)
274
+ this.#addTelemetry('datadog.dogstatsd.client.bytes_dropped', telemetry.bytesDropped)
275
+ this.#addTelemetry('datadog.dogstatsd.client.packets_sent', telemetry.packetsSent)
276
+ this.#addTelemetry('datadog.dogstatsd.client.packets_dropped', telemetry.packetsDropped)
277
+
278
+ telemetry.bytesSent = 0
279
+ telemetry.bytesDropped = 0
280
+ telemetry.packetsSent = 0
281
+ telemetry.packetsDropped = 0
282
+ for (let index = 0; index < TYPE_LABELS.length; index++) {
283
+ telemetry.aggregatedContextsByType[index] = 0
284
+ telemetry.metricsByType[index] = 0
285
+ }
286
+
287
+ this.#flush(telemetry.payload, false, track)
288
+ }
289
+
290
+ /**
291
+ * @param {DogStatsDBufferState} state - Payload state to flush
292
+ * @param {boolean} recordTelemetry - Whether to record the transport outcome
293
+ * @param {((send: (done?: () => void) => void) => void)} [track] - Tracks a non-serverless delivery
294
+ * @returns {void}
295
+ */
296
+ #flush (state, recordTelemetry, track) {
297
+ const queue = this._enqueue(state)
72
298
 
73
299
  if (queue.length === 0) return
74
300
 
75
- log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
301
+ log.debug('Flushing %s metrics via %s', queue.length, this.#httpOptions ? 'HTTP' : 'UDP')
76
302
 
77
- this._queue = []
303
+ state.queue = []
78
304
 
79
- if (this._httpOptions) {
80
- this._sendHttp(queue)
81
- } else {
82
- this._sendUdp(queue)
305
+ const send = complete => {
306
+ if (this.#httpOptions) this._sendHttp(queue, recordTelemetry, complete)
307
+ else this._sendUdp(queue, recordTelemetry, complete)
83
308
  }
309
+
310
+ if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.track(send)
311
+ if (track) return track(send)
312
+ send()
84
313
  }
85
314
 
86
- _sendHttp (queue) {
315
+ /**
316
+ * @param {number} bytes - Number of bytes sent
317
+ * @returns {void}
318
+ */
319
+ #recordSent (bytes) {
320
+ const telemetry = this.telemetry
321
+
322
+ telemetry.bytesSent += bytes
323
+ telemetry.packetsSent++
324
+ }
325
+
326
+ /**
327
+ * @param {number} bytes - Number of bytes dropped
328
+ * @param {number} [packets] - Number of packets dropped
329
+ * @returns {void}
330
+ */
331
+ #recordDropped (bytes, packets = 1) {
332
+ const telemetry = this.telemetry
333
+
334
+ telemetry.bytesDropped += bytes
335
+ telemetry.packetsDropped += packets
336
+ }
337
+
338
+ /**
339
+ * Send metrics to the agent via HTTP
340
+ *
341
+ * @param {Buffer[]} queue - The metrics to send
342
+ * @param {boolean} recordTelemetry - Whether to record the transport outcome
343
+ * @param {() => void} [done] - Called after delivery completes
344
+ * @returns {void}
345
+ * @memberof DogStatsDClient
346
+ */
347
+ _sendHttp (queue, recordTelemetry, done) {
87
348
  const buffer = Buffer.concat(queue)
88
- request(buffer, this._httpOptions, (err) => {
89
- if (err) {
90
- log.error('DogStatsDClient: HTTP error from agent: %s', err.message, err)
91
- if (err.status === 404) {
349
+ request(buffer, this.#httpOptions, (error, _result, _statusCode, _headers, dropped) => {
350
+ if (dropped) {
351
+ if (recordTelemetry && this.telemetry) {
352
+ this.#recordDropped(buffer.length)
353
+ }
354
+ done?.()
355
+ } else if (error) {
356
+ log.error('DogStatsDClient: HTTP error from agent: %s', error.message, error)
357
+ if (error.status === 404) {
92
358
  // Inside this if-block, we have connectivity to the agent, but
93
359
  // we're not getting a 200 from the proxy endpoint. If it's a 404,
94
360
  // then we know we'll never have the endpoint, so just clear out the
95
361
  // options. Either way, we can give UDP a try.
96
- this._httpOptions = undefined
362
+ this.#httpOptions = undefined
97
363
  }
98
- this._sendUdp(queue)
364
+ this._sendUdp(queue, recordTelemetry, done)
365
+ } else {
366
+ if (recordTelemetry && this.telemetry) this.#recordSent(buffer.length)
367
+ done?.()
99
368
  }
100
369
  })
101
370
  }
102
371
 
103
- _sendUdp (queue) {
372
+ /**
373
+ * Send metrics to the agent via UDP
374
+ *
375
+ * @param {Buffer[]} queue - The metrics to send
376
+ * @param {boolean} recordTelemetry - Whether to record the transport outcome
377
+ * @param {() => void} [done] - Called after delivery completes
378
+ * @returns {void}
379
+ * @memberof DogStatsDClient
380
+ */
381
+ _sendUdp (queue, recordTelemetry, done) {
104
382
  // dgram resolves the local address via the instrumented dns.lookup when it
105
383
  // binds on first send; the noop store keeps that self-traffic off the trace.
106
384
  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)
385
+ if (this.#family === 0) {
386
+ this.#lookup(this.#host, (error, address, family) => {
387
+ if (error) {
388
+ if (recordTelemetry && this.telemetry) {
389
+ let bytes = 0
390
+ for (const buffer of queue) {
391
+ bytes += buffer.length
392
+ }
393
+ this.#recordDropped(bytes, queue.length)
394
+ }
395
+ log.error('DogStatsDClient: Host not found', error)
396
+ return done?.()
397
+ }
398
+ this._sendUdpFromQueue(queue, address, family, recordTelemetry, done)
111
399
  })
112
400
  } else {
113
- this._sendUdpFromQueue(queue, this._host, this._family)
401
+ this._sendUdpFromQueue(queue, this.#host, this.#family, recordTelemetry, done)
114
402
  }
115
403
  })
116
404
  }
117
405
 
118
- _sendUdpFromQueue (queue, address, family) {
119
- const socket = family === 6 ? this._udp6 : this._udp4
406
+ /**
407
+ * Send metrics to the agent via UDP from queue
408
+ *
409
+ * @param {Buffer[]} queue - The metrics to send
410
+ * @param {string} address - The address to send the metrics to
411
+ * @param {number} family - The family of the address
412
+ * @param {boolean} recordTelemetry - Whether to record the transport outcome
413
+ * @param {() => void} [done] - Called after every packet completes
414
+ * @returns {void}
415
+ * @memberof DogStatsDClient
416
+ */
417
+ _sendUdpFromQueue (queue, address, family, recordTelemetry, done) {
418
+ const socket = family === 6 ? this.#udp6 : this.#udp4
419
+ let pending = queue.length
420
+ const complete = () => {
421
+ if (--pending === 0) done?.()
422
+ }
120
423
 
121
424
  for (const buffer of queue) {
122
425
  log.debug('Sending to DogStatsD: %s', buffer)
123
- socket.send(buffer, 0, buffer.length, this._port, address)
426
+
427
+ if (!this.telemetry && !done) {
428
+ socket.send(buffer, 0, buffer.length, this.#port, address)
429
+ continue
430
+ }
431
+
432
+ try {
433
+ socket.send(buffer, 0, buffer.length, this.#port, address, (error) => {
434
+ if (error) {
435
+ if (recordTelemetry && this.telemetry) {
436
+ this.#recordDropped(buffer.length)
437
+ }
438
+ log.error('DogStatsDClient: UDP error', error)
439
+ } else if (recordTelemetry && this.telemetry) {
440
+ this.#recordSent(buffer.length)
441
+ }
442
+ complete()
443
+ })
444
+ } catch (error) {
445
+ log.error('DogStatsDClient: UDP error sending metrics', error)
446
+ complete()
447
+ }
124
448
  }
125
449
  }
126
450
 
127
- _add (stat, value, type, tags) {
451
+ /**
452
+ * Add a metric to the queue
453
+ *
454
+ * @param {string} stat - The metric name
455
+ * @param {number} value - The metric value
456
+ * @param {string} type - The metric type
457
+ * @param {string[]} tags - The metric tags
458
+ * @param {string} [tagsPrefix] - Serialized global tags
459
+ * @param {DogStatsDBufferState} [state] - Payload state receiving the metric
460
+ * @returns {void}
461
+ * @memberof DogStatsDClient
462
+ */
463
+ _add (stat, value, type, tags, tagsPrefix = this.#tagsPrefix, state = this.#metrics) {
128
464
  let message = `${stat}:${value}|${type}`
129
465
 
130
466
  if (tags?.length) {
131
- message += this.#tagsPrefix
132
- ? `${this.#tagsPrefix},${tags.join(',')}`
467
+ message += tagsPrefix
468
+ ? `${tagsPrefix},${tags.join(',')}`
133
469
  : `|#${tags.join(',')}`
134
470
  } else {
135
- message += this.#tagsPrefix
471
+ message += tagsPrefix
136
472
  }
137
473
 
138
474
  if (entityId) {
139
475
  message += `|c:${entityId}`
140
476
  }
141
477
 
142
- this._write(`${message}\n`)
478
+ this._write(`${message}\n`, state)
143
479
  }
144
480
 
145
- _write (message) {
146
- const offset = Buffer.byteLength(message)
481
+ /**
482
+ * @param {string} stat - Telemetry metric name
483
+ * @param {number} value - Telemetry metric value
484
+ * @param {string} [typeLabel] - Submitted or aggregated metric type
485
+ * @returns {void}
486
+ */
487
+ #addTelemetry (stat, value, typeLabel) {
488
+ const tags = typeLabel === undefined ? undefined : [`metrics_type:${typeLabel}`]
489
+ const tagsPrefix = this.#httpOptions ? this.#telemetryHttpTagsPrefix : this.#telemetryUdpTagsPrefix
147
490
 
148
- if (this._offset + offset > MAX_BUFFER_SIZE) {
149
- this._enqueue()
150
- }
491
+ this._add(stat, value, TYPE_COUNTER, tags, tagsPrefix, this.telemetry.payload)
492
+ }
151
493
 
152
- this._offset += offset
153
- this._buffer += message
494
+ /**
495
+ * Write a message to the queue
496
+ *
497
+ * @param {string} message - The message to write
498
+ * @param {DogStatsDBufferState} [state] - Payload state receiving the message
499
+ * @returns {void}
500
+ * @memberof DogStatsDClient
501
+ */
502
+ _write (message, state = this.#metrics) {
503
+ const offset = Buffer.byteLength(message)
504
+ if (state.offset + offset > MAX_BUFFER_SIZE) {
505
+ this._enqueue(state)
506
+ }
507
+ state.offset += offset
508
+ state.message += message
154
509
  }
155
510
 
156
- _enqueue () {
157
- if (this._offset > 0) {
158
- this._queue.push(Buffer.from(this._buffer))
159
- this._buffer = ''
160
- this._offset = 0
511
+ /**
512
+ * Enqueue a message to the queue
513
+ *
514
+ * @param {DogStatsDBufferState} [state] - Payload state to enqueue
515
+ * @returns {Buffer[]} Queued payloads
516
+ * @memberof DogStatsDClient
517
+ */
518
+ _enqueue (state = this.#metrics) {
519
+ if (state.offset > 0) {
520
+ state.queue.push(Buffer.from(state.message))
521
+ state.message = ''
522
+ state.offset = 0
161
523
  }
162
524
 
163
- return this._queue
525
+ return state.queue
164
526
  }
165
527
 
166
528
  _socket (type) {
@@ -205,19 +567,65 @@ class DogStatsDClient {
205
567
  }
206
568
  }
207
569
 
570
+ /**
571
+ * @param {DogStatsDClientOptions} options - DogStatsD transport options
572
+ * @returns {MetricsAggregationClient} Aggregating client with shared telemetry
573
+ */
574
+ function createMetricsAggregationClient (options) {
575
+ const client = new DogStatsDClient(options, true)
576
+
577
+ return new MetricsAggregationClient(client)
578
+ }
579
+
208
580
  class MetricsAggregationClient {
581
+ #metricsByType
582
+
583
+ /** @type {DogStatsDTelemetryState|undefined} */
584
+ #telemetry
585
+
586
+ /**
587
+ * @param {DogStatsDClient} client - DogStatsD transport client
588
+ */
209
589
  constructor (client) {
210
590
  this._client = client
591
+ this.#telemetry = client.telemetry
592
+ this.#metricsByType = this.#telemetry?.metricsByType ?? [0, 0, 0, 0]
211
593
 
212
594
  this.reset()
213
595
  }
214
596
 
215
- flush () {
216
- this._captureCounters()
217
- this._captureGauges()
218
- this._captureHistograms()
597
+ /**
598
+ * Recomputes the wrapped client's cached tags (e.g. after a MicroVM clone resume).
599
+ * @param {string[]} tags - DogStatsD-formatted tags (e.g. `['key:value']`)
600
+ */
601
+ updateTags (tags) {
602
+ this._client.updateTags(tags)
603
+ }
604
+
605
+ /**
606
+ * @param {boolean|(() => void)} [forceTelemetry] - Whether to ignore the telemetry interval, or completion callback
607
+ * @param {() => void} [done] - Called after serverless deliveries complete
608
+ * @returns {void}
609
+ */
610
+ flush (forceTelemetry = false, done) {
611
+ if (typeof forceTelemetry === 'function') {
612
+ done = forceTelemetry
613
+ forceTelemetry = false
614
+ }
615
+
616
+ const counters = this._captureCounters()
617
+ const gauges = this._captureGauges()
618
+ const histograms = this._captureHistograms()
619
+ const telemetry = this.#telemetry
219
620
 
220
- this._client.flush()
621
+ if (telemetry) {
622
+ telemetry.aggregatedContextsByType[TYPE_COUNTER_INDEX] += counters
623
+ telemetry.aggregatedContextsByType[TYPE_GAUGE_INDEX] += gauges
624
+ telemetry.aggregatedContextsByType[TYPE_HISTOGRAM_INDEX] += histograms
625
+ }
626
+
627
+ if (forceTelemetry) this._client.flush(true, done)
628
+ else this._client.flush(done)
221
629
  }
222
630
 
223
631
  reset () {
@@ -229,6 +637,7 @@ class MetricsAggregationClient {
229
637
  // TODO: Aggregate with a histogram and send the buckets to the client.
230
638
  distribution (name, value, tags) {
231
639
  this._client.distribution(name, value, tags)
640
+ this.#metricsByType[TYPE_DISTRIBUTION_INDEX]++
232
641
  }
233
642
 
234
643
  boolean (name, value, tags) {
@@ -271,24 +680,45 @@ class MetricsAggregationClient {
271
680
  this.count(name, -count, tags)
272
681
  }
273
682
 
683
+ /**
684
+ * @returns {number} Number of gauge contexts flushed
685
+ */
274
686
  _captureGauges () {
275
- this._captureTree(this._gauges, (node, name, tags) => {
687
+ let metrics = 0
688
+ const contexts = this._captureTree(this._gauges, (node, name, tags) => {
689
+ metrics += node.submissions
276
690
  this._client.gauge(name, node.value, tags)
277
691
  })
278
692
 
693
+ this.#metricsByType[TYPE_GAUGE_INDEX] += metrics
279
694
  this._gauges.clear()
695
+
696
+ return contexts
280
697
  }
281
698
 
699
+ /**
700
+ * @returns {number} Number of counter contexts flushed
701
+ */
282
702
  _captureCounters () {
283
- this._captureTree(this._counters, (node, name, tags) => {
703
+ let metrics = 0
704
+ const contexts = this._captureTree(this._counters, (node, name, tags) => {
705
+ metrics += node.submissions
284
706
  this._client.increment(name, node.value, tags)
285
707
  })
286
708
 
709
+ this.#metricsByType[TYPE_COUNTER_INDEX] += metrics
287
710
  this._counters.clear()
711
+
712
+ return contexts
288
713
  }
289
714
 
715
+ /**
716
+ * @returns {number} Number of histogram contexts flushed
717
+ */
290
718
  _captureHistograms () {
291
- this._captureTree(this._histograms, (node, name, tags) => {
719
+ let metrics = 0
720
+ const contexts = this._captureTree(this._histograms, (node, name, tags) => {
721
+ metrics += node.submissions
292
722
  const stats = node.value
293
723
 
294
724
  this._client.gauge(`${name}.min`, stats.min, tags)
@@ -301,25 +731,49 @@ class MetricsAggregationClient {
301
731
  this._client.gauge(`${name}.95percentile`, stats.p95, tags)
302
732
  })
303
733
 
734
+ this.#metricsByType[TYPE_HISTOGRAM_INDEX] += metrics
304
735
  this._histograms.clear()
736
+
737
+ return contexts
305
738
  }
306
739
 
740
+ /**
741
+ * @param {Map<string, MetricNode>} tree - Metric context tree
742
+ * @param {CaptureMetric} fn - Called for every context with submissions
743
+ * @returns {number} Number of contexts with submissions
744
+ */
307
745
  _captureTree (tree, fn) {
746
+ let contexts = 0
747
+
308
748
  for (const [name, root] of tree) {
309
- this._captureNode(root, name, [], fn)
749
+ contexts += this._captureNode(root, name, [], fn)
310
750
  }
751
+
752
+ return contexts
311
753
  }
312
754
 
755
+ /**
756
+ * @param {MetricNode} node - Current metric context node
757
+ * @param {string} name - Metric name
758
+ * @param {string[]} tags - Current metric tags
759
+ * @param {CaptureMetric} fn - Called for every context with submissions
760
+ * @returns {number} Number of contexts with submissions
761
+ */
313
762
  _captureNode (node, name, tags, fn) {
314
- if (node.touched) {
763
+ let contexts = 0
764
+
765
+ if (node.submissions !== 0) {
315
766
  fn(node, name, tags)
767
+ contexts++
316
768
  }
317
769
 
318
770
  for (const [tag, next] of node.nodes) {
319
771
  tags.push(tag)
320
- this._captureNode(next, name, tags, fn)
772
+ contexts += this._captureNode(next, name, tags, fn)
321
773
  tags.pop()
322
774
  }
775
+
776
+ return contexts
323
777
  }
324
778
 
325
779
  _ensureTree (tree, name, tags = [], value) {
@@ -333,7 +787,7 @@ class MetricsAggregationClient {
333
787
  node = this._ensureNode(node.nodes, tag, value)
334
788
  }
335
789
 
336
- node.touched = true
790
+ node.submissions++
337
791
 
338
792
  return node
339
793
  }
@@ -342,7 +796,7 @@ class MetricsAggregationClient {
342
796
  let node = container.get(key)
343
797
 
344
798
  if (!node) {
345
- node = { nodes: new Map(), touched: false, value }
799
+ node = { nodes: new Map(), submissions: 0, value }
346
800
 
347
801
  if (typeof key === 'string') {
348
802
  container.set(key, node)
@@ -362,14 +816,20 @@ class CustomMetrics {
362
816
  #client
363
817
  constructor (config) {
364
818
  const clientConfig = DogStatsDClient.generateClientConfig(config)
365
- this.#client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
819
+ this.#client = createMetricsAggregationClient(clientConfig)
820
+
821
+ // CustomMetrics has process-lifetime flush handlers and no stop hook, so this shares that lifetime.
822
+ identityRefreshChannel.subscribe(() => {
823
+ this.#client.updateTags(DogStatsDClient.generateClientConfig(config).tags)
824
+ })
366
825
 
367
826
  const flush = this.flush.bind(this)
368
827
 
369
828
  // TODO(bengl) this magic number should be configurable
370
829
  setInterval(flush, 10 * 1000).unref?.()
371
830
 
372
- globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(flush)
831
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(() => this.#client.flush(true))
832
+ registerTelemetryFlusher(done => this.flush(done))
373
833
  }
374
834
 
375
835
  increment (stat, value = 1, tags) {
@@ -392,8 +852,8 @@ class CustomMetrics {
392
852
  this.#client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
393
853
  }
394
854
 
395
- flush () {
396
- return this.#client.flush()
855
+ flush (done) {
856
+ return this.#client.flush(false, done)
397
857
  }
398
858
 
399
859
  /**
@@ -419,4 +879,5 @@ module.exports = {
419
879
  DogStatsDClient,
420
880
  CustomMetrics,
421
881
  MetricsAggregationClient,
882
+ createMetricsAggregationClient,
422
883
  }