dd-trace 5.124.0 → 5.125.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 (201) 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/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -2,7 +2,11 @@
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')
@@ -14,42 +18,170 @@ const { createServerlessDeliveryTracker } = require('./serverless')
14
18
  const legacyStorage = storage('legacy')
15
19
 
16
20
  const MAX_BUFFER_SIZE = 1024 // limit from the agent
21
+ const TELEMETRY_INTERVAL = 10_000
17
22
 
18
23
  const TYPE_COUNTER = 'c'
19
24
  const TYPE_GAUGE = 'g'
20
25
  const TYPE_DISTRIBUTION = 'd'
21
26
  const TYPE_HISTOGRAM = 'h'
22
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
+
23
83
  /**
24
84
  * @import { DogStatsD } from "../../../index.d.ts"
25
85
  * @implements {DogStatsD}
26
86
  */
27
87
  class DogStatsDClient {
88
+ #family
89
+ #host
90
+ #httpOptions
28
91
  #lookup
29
- #tagsPrefix
92
+ #metrics = { message: '', offset: 0, queue: [] }
93
+ #port
30
94
  #serverlessDeliveryTracker
95
+ #tagsPrefix
96
+ #telemetryHttpTagsPrefix
97
+ #telemetryUdpTagsPrefix
98
+ #udp4
99
+ #udp6
100
+
101
+ /** @type {DogStatsDTelemetryState|undefined} */
102
+ telemetry
31
103
 
32
- constructor (options) {
104
+ /**
105
+ * @param {DogStatsDClientOptions} options - DogStatsD transport options
106
+ * @param {boolean} [telemetryEnabled] - Whether to collect client telemetry
107
+ */
108
+ constructor (options, telemetryEnabled = false) {
33
109
  this.#lookup = options.lookup
34
110
  if (options.metricsProxyUrl) {
35
- this._httpOptions = {
111
+ this.#httpOptions = {
36
112
  method: 'POST',
113
+ retry: false,
37
114
  url: options.metricsProxyUrl.toString(),
38
115
  path: '/dogstatsd/v2/proxy',
39
116
  }
40
117
  }
41
118
 
42
- this._host = options.host
43
- this._family = isIP(this._host)
44
- this._port = options.port
45
- this._tags = options.tags
46
- this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
47
- this._queue = []
119
+ this.#family = isIP(options.host)
120
+ this.#host = options.host
121
+ this.#port = options.port
48
122
  this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
49
- this._buffer = ''
50
- this._offset = 0
51
- this._udp4 = this._socket('udp4')
52
- this._udp6 = this._socket('udp6')
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`
53
185
  }
54
186
 
55
187
  increment (stat, value, tags) {
@@ -72,68 +204,218 @@ class DogStatsDClient {
72
204
  this._add(stat, value, TYPE_HISTOGRAM, tags)
73
205
  }
74
206
 
75
- flush (done) {
76
- 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
+ }
77
217
 
78
- if (queue.length === 0) {
79
- if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
80
- return done?.()
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
+ }
81
229
  }
82
230
 
83
- log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
231
+ this.#flush(this.#metrics, true, track)
232
+ if (this.telemetry) this.#flushTelemetry(forceTelemetry, track)
84
233
 
85
- this._queue = []
234
+ if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
235
+ complete?.()
236
+ }
86
237
 
87
- const send = complete => {
88
- if (this._httpOptions) this._sendHttp(queue, complete)
89
- else this._sendUdp(queue, complete)
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
+ )
90
272
  }
91
- if (this.#serverlessDeliveryTracker) {
92
- this.#serverlessDeliveryTracker.track(send)
93
- return this.#serverlessDeliveryTracker.waitForIdle(done)
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)
298
+
299
+ if (queue.length === 0) return
300
+
301
+ log.debug('Flushing %s metrics via %s', queue.length, this.#httpOptions ? 'HTTP' : 'UDP')
302
+
303
+ state.queue = []
304
+
305
+ const send = complete => {
306
+ if (this.#httpOptions) this._sendHttp(queue, recordTelemetry, complete)
307
+ else this._sendUdp(queue, recordTelemetry, complete)
94
308
  }
95
- send(done)
309
+
310
+ if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.track(send)
311
+ if (track) return track(send)
312
+ send()
313
+ }
314
+
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
96
336
  }
97
337
 
98
- _sendHttp (queue, done) {
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) {
99
348
  const buffer = Buffer.concat(queue)
100
- request(buffer, this._httpOptions, (err) => {
101
- if (err) {
102
- log.error('DogStatsDClient: HTTP error from agent: %s', err.message, err)
103
- 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) {
104
358
  // Inside this if-block, we have connectivity to the agent, but
105
359
  // we're not getting a 200 from the proxy endpoint. If it's a 404,
106
360
  // then we know we'll never have the endpoint, so just clear out the
107
361
  // options. Either way, we can give UDP a try.
108
- this._httpOptions = undefined
362
+ this.#httpOptions = undefined
109
363
  }
110
- this._sendUdp(queue, done)
364
+ this._sendUdp(queue, recordTelemetry, done)
111
365
  } else {
366
+ if (recordTelemetry && this.telemetry) this.#recordSent(buffer.length)
112
367
  done?.()
113
368
  }
114
369
  })
115
370
  }
116
371
 
117
- _sendUdp (queue, done) {
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) {
118
382
  // dgram resolves the local address via the instrumented dns.lookup when it
119
383
  // binds on first send; the noop store keeps that self-traffic off the trace.
120
384
  legacyStorage.run({ noop: true }, () => {
121
- if (this._family === 0) {
122
- this.#lookup(this._host, (error, address, family) => {
385
+ if (this.#family === 0) {
386
+ this.#lookup(this.#host, (error, address, family) => {
123
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
+ }
124
395
  log.error('DogStatsDClient: Host not found', error)
125
396
  return done?.()
126
397
  }
127
- this._sendUdpFromQueue(queue, address, family, done)
398
+ this._sendUdpFromQueue(queue, address, family, recordTelemetry, done)
128
399
  })
129
400
  } else {
130
- this._sendUdpFromQueue(queue, this._host, this._family, done)
401
+ this._sendUdpFromQueue(queue, this.#host, this.#family, recordTelemetry, done)
131
402
  }
132
403
  })
133
404
  }
134
405
 
135
- _sendUdpFromQueue (queue, address, family, done) {
136
- 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
137
419
  let pending = queue.length
138
420
  const complete = () => {
139
421
  if (--pending === 0) done?.()
@@ -141,8 +423,24 @@ class DogStatsDClient {
141
423
 
142
424
  for (const buffer of queue) {
143
425
  log.debug('Sending to DogStatsD: %s', buffer)
426
+
427
+ if (!this.telemetry && !done) {
428
+ socket.send(buffer, 0, buffer.length, this.#port, address)
429
+ continue
430
+ }
431
+
144
432
  try {
145
- socket.send(buffer, 0, buffer.length, this._port, address, complete)
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
+ })
146
444
  } catch (error) {
147
445
  log.error('DogStatsDClient: UDP error sending metrics', error)
148
446
  complete()
@@ -150,43 +448,81 @@ class DogStatsDClient {
150
448
  }
151
449
  }
152
450
 
153
- _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) {
154
464
  let message = `${stat}:${value}|${type}`
155
465
 
156
466
  if (tags?.length) {
157
- message += this.#tagsPrefix
158
- ? `${this.#tagsPrefix},${tags.join(',')}`
467
+ message += tagsPrefix
468
+ ? `${tagsPrefix},${tags.join(',')}`
159
469
  : `|#${tags.join(',')}`
160
470
  } else {
161
- message += this.#tagsPrefix
471
+ message += tagsPrefix
162
472
  }
163
473
 
164
474
  if (entityId) {
165
475
  message += `|c:${entityId}`
166
476
  }
167
477
 
168
- this._write(`${message}\n`)
478
+ this._write(`${message}\n`, state)
169
479
  }
170
480
 
171
- _write (message) {
172
- 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
173
490
 
174
- if (this._offset + offset > MAX_BUFFER_SIZE) {
175
- this._enqueue()
176
- }
491
+ this._add(stat, value, TYPE_COUNTER, tags, tagsPrefix, this.telemetry.payload)
492
+ }
177
493
 
178
- this._offset += offset
179
- 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
180
509
  }
181
510
 
182
- _enqueue () {
183
- if (this._offset > 0) {
184
- this._queue.push(Buffer.from(this._buffer))
185
- this._buffer = ''
186
- 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
187
523
  }
188
524
 
189
- return this._queue
525
+ return state.queue
190
526
  }
191
527
 
192
528
  _socket (type) {
@@ -231,19 +567,65 @@ class DogStatsDClient {
231
567
  }
232
568
  }
233
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
+
234
580
  class MetricsAggregationClient {
581
+ #metricsByType
582
+
583
+ /** @type {DogStatsDTelemetryState|undefined} */
584
+ #telemetry
585
+
586
+ /**
587
+ * @param {DogStatsDClient} client - DogStatsD transport client
588
+ */
235
589
  constructor (client) {
236
590
  this._client = client
591
+ this.#telemetry = client.telemetry
592
+ this.#metricsByType = this.#telemetry?.metricsByType ?? [0, 0, 0, 0]
237
593
 
238
594
  this.reset()
239
595
  }
240
596
 
241
- flush (done) {
242
- this._captureCounters()
243
- this._captureGauges()
244
- 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
+ }
245
615
 
246
- this._client.flush(done)
616
+ const counters = this._captureCounters()
617
+ const gauges = this._captureGauges()
618
+ const histograms = this._captureHistograms()
619
+ const telemetry = this.#telemetry
620
+
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)
247
629
  }
248
630
 
249
631
  reset () {
@@ -255,6 +637,7 @@ class MetricsAggregationClient {
255
637
  // TODO: Aggregate with a histogram and send the buckets to the client.
256
638
  distribution (name, value, tags) {
257
639
  this._client.distribution(name, value, tags)
640
+ this.#metricsByType[TYPE_DISTRIBUTION_INDEX]++
258
641
  }
259
642
 
260
643
  boolean (name, value, tags) {
@@ -297,24 +680,45 @@ class MetricsAggregationClient {
297
680
  this.count(name, -count, tags)
298
681
  }
299
682
 
683
+ /**
684
+ * @returns {number} Number of gauge contexts flushed
685
+ */
300
686
  _captureGauges () {
301
- 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
302
690
  this._client.gauge(name, node.value, tags)
303
691
  })
304
692
 
693
+ this.#metricsByType[TYPE_GAUGE_INDEX] += metrics
305
694
  this._gauges.clear()
695
+
696
+ return contexts
306
697
  }
307
698
 
699
+ /**
700
+ * @returns {number} Number of counter contexts flushed
701
+ */
308
702
  _captureCounters () {
309
- 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
310
706
  this._client.increment(name, node.value, tags)
311
707
  })
312
708
 
709
+ this.#metricsByType[TYPE_COUNTER_INDEX] += metrics
313
710
  this._counters.clear()
711
+
712
+ return contexts
314
713
  }
315
714
 
715
+ /**
716
+ * @returns {number} Number of histogram contexts flushed
717
+ */
316
718
  _captureHistograms () {
317
- 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
318
722
  const stats = node.value
319
723
 
320
724
  this._client.gauge(`${name}.min`, stats.min, tags)
@@ -327,25 +731,49 @@ class MetricsAggregationClient {
327
731
  this._client.gauge(`${name}.95percentile`, stats.p95, tags)
328
732
  })
329
733
 
734
+ this.#metricsByType[TYPE_HISTOGRAM_INDEX] += metrics
330
735
  this._histograms.clear()
736
+
737
+ return contexts
331
738
  }
332
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
+ */
333
745
  _captureTree (tree, fn) {
746
+ let contexts = 0
747
+
334
748
  for (const [name, root] of tree) {
335
- this._captureNode(root, name, [], fn)
749
+ contexts += this._captureNode(root, name, [], fn)
336
750
  }
751
+
752
+ return contexts
337
753
  }
338
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
+ */
339
762
  _captureNode (node, name, tags, fn) {
340
- if (node.touched) {
763
+ let contexts = 0
764
+
765
+ if (node.submissions !== 0) {
341
766
  fn(node, name, tags)
767
+ contexts++
342
768
  }
343
769
 
344
770
  for (const [tag, next] of node.nodes) {
345
771
  tags.push(tag)
346
- this._captureNode(next, name, tags, fn)
772
+ contexts += this._captureNode(next, name, tags, fn)
347
773
  tags.pop()
348
774
  }
775
+
776
+ return contexts
349
777
  }
350
778
 
351
779
  _ensureTree (tree, name, tags = [], value) {
@@ -359,7 +787,7 @@ class MetricsAggregationClient {
359
787
  node = this._ensureNode(node.nodes, tag, value)
360
788
  }
361
789
 
362
- node.touched = true
790
+ node.submissions++
363
791
 
364
792
  return node
365
793
  }
@@ -368,7 +796,7 @@ class MetricsAggregationClient {
368
796
  let node = container.get(key)
369
797
 
370
798
  if (!node) {
371
- node = { nodes: new Map(), touched: false, value }
799
+ node = { nodes: new Map(), submissions: 0, value }
372
800
 
373
801
  if (typeof key === 'string') {
374
802
  container.set(key, node)
@@ -388,14 +816,19 @@ class CustomMetrics {
388
816
  #client
389
817
  constructor (config) {
390
818
  const clientConfig = DogStatsDClient.generateClientConfig(config)
391
- 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
+ })
392
825
 
393
826
  const flush = this.flush.bind(this)
394
827
 
395
828
  // TODO(bengl) this magic number should be configurable
396
829
  setInterval(flush, 10 * 1000).unref?.()
397
830
 
398
- globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(flush)
831
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(() => this.#client.flush(true))
399
832
  registerTelemetryFlusher(done => this.flush(done))
400
833
  }
401
834
 
@@ -420,7 +853,7 @@ class CustomMetrics {
420
853
  }
421
854
 
422
855
  flush (done) {
423
- return this.#client.flush(done)
856
+ return this.#client.flush(false, done)
424
857
  }
425
858
 
426
859
  /**
@@ -446,4 +879,5 @@ module.exports = {
446
879
  DogStatsDClient,
447
880
  CustomMetrics,
448
881
  MetricsAggregationClient,
882
+ createMetricsAggregationClient,
449
883
  }