dd-trace 5.104.0 → 5.105.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 (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -1,22 +1,47 @@
1
1
  'use strict'
2
2
 
3
- const DEFAULT_MIN_SIZE = 2 * 1024 * 1024 // 2MB
3
+ const DEFAULT_MIN_SIZE = 1024 * 1024 // 1 MiB
4
+ // Number of consecutive `reset()` calls whose peak usage stayed under
5
+ // `SHRINK_USAGE_RATIO * buffer.length` before the buffer halves. Picked high
6
+ // enough that a one-off burst keeps the grown buffer warm.
7
+ const SHRINK_AFTER_FLUSHES = 32
8
+ // Peak fraction of the current buffer the next flush must beat to keep the
9
+ // shrink streak from advancing. 1/4 — a quarter — matches the doubling growth
10
+ // shape: after a halving step the post-shrink fill is the prior peak doubled,
11
+ // still under 50 %.
12
+ const SHRINK_USAGE_RATIO = 4
4
13
 
5
14
  /**
6
- * Represents a chunk of a Msgpack payload. Exposes a subset of Array and Buffer
7
- * interfaces so that it can be used seamlessly by any encoder code that expects
8
- * either.
15
+ * Resizable msgpack write buffer. Owns the byte-layout primitives the encoder
16
+ * layer dispatches into; callers reach the underlying `Buffer` only when they
17
+ * need to assemble a fused write (pre-computed prefixes, span-id payloads).
18
+ *
19
+ * Growth doubles the capacity per `reserve`; shrink halves it after
20
+ * `SHRINK_AFTER_FLUSHES` consecutive `reset()` calls left the buffer barely
21
+ * filled. Both stop at `minSize` so callers can pin a floor (CI Visibility's
22
+ * payload prefix chunk uses ~2 KiB).
9
23
  */
10
24
  class MsgpackChunk {
11
25
  #minSize
26
+ #lowUsageStreak = 0
12
27
 
13
28
  constructor (minSize = DEFAULT_MIN_SIZE) {
14
29
  this.buffer = Buffer.allocUnsafe(minSize)
15
- this.view = new DataView(this.buffer.buffer)
30
+ // `Buffer.allocUnsafe` pools small allocations, so `buffer.buffer` may be a
31
+ // shared slab; pass `byteOffset` / `byteLength` so the view spans only our slice.
32
+ this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength)
16
33
  this.length = 0
17
34
  this.#minSize = minSize
18
35
  }
19
36
 
37
+ /**
38
+ * Emit `value` as a msgpack string (fixstr for < 32 bytes, str32 otherwise).
39
+ * Returns the number of bytes written so callers can subarray the underlying
40
+ * buffer at the resulting position.
41
+ *
42
+ * @param {string} value
43
+ * @returns {number}
44
+ */
20
45
  write (value) {
21
46
  const length = Buffer.byteLength(value)
22
47
  const offset = this.length
@@ -38,10 +63,24 @@ class MsgpackChunk {
38
63
  return this.length - offset
39
64
  }
40
65
 
66
+ /**
67
+ * Copy this chunk's used bytes into `target` starting at `target[0]`. Used
68
+ * by `AgentEncoder.makePayload` to assemble the final wire buffer.
69
+ *
70
+ * @param {Buffer} target
71
+ * @param {number} sourceStart
72
+ * @param {number} sourceEnd
73
+ */
41
74
  copy (target, sourceStart, sourceEnd) {
42
- target.set(new Uint8Array(this.buffer.buffer, sourceStart, sourceEnd - sourceStart))
75
+ this.buffer.copy(target, 0, sourceStart, sourceEnd)
43
76
  }
44
77
 
78
+ /**
79
+ * Append a raw byte sequence to the chunk. Caller-supplied bytes are
80
+ * trusted; this is the fused-prefix path.
81
+ *
82
+ * @param {Uint8Array | Buffer} array
83
+ */
45
84
  set (array) {
46
85
  const length = this.length
47
86
 
@@ -50,20 +89,365 @@ class MsgpackChunk {
50
89
  this.buffer.set(array, length)
51
90
  }
52
91
 
92
+ /**
93
+ * Reserve `size` more bytes after the current cursor, growing the backing
94
+ * buffer if needed. The cursor advances unconditionally so subsequent
95
+ * writes can assume the room is available.
96
+ *
97
+ * @param {number} size
98
+ */
53
99
  reserve (size) {
54
- if (this.length + size > this.buffer.length) {
55
- const minSize = this.#minSize
56
- this.#resize(minSize * Math.ceil((this.length + size) / minSize))
100
+ const needed = this.length + size
101
+
102
+ if (needed > this.buffer.length) {
103
+ let newSize = this.buffer.length
104
+ // `*= 2` instead of `<<= 1`: `1073741824 << 1` is negative as int32,
105
+ // and msgpack values can legitimately reach the multi-GiB range.
106
+ while (newSize < needed) newSize *= 2
107
+ this.#resize(newSize)
57
108
  }
58
109
 
59
110
  this.length += size
60
111
  }
61
112
 
113
+ /**
114
+ * Mark the buffer as flushed: zero the cursor and, when the previous flush
115
+ * barely filled the buffer for `SHRINK_AFTER_FLUSHES` consecutive resets,
116
+ * halve the backing buffer. A single high-watermark flush resets the
117
+ * streak. Long-lived encoders can therefore grow under bursts and give the
118
+ * memory back during quiet periods without the user having to recreate the
119
+ * chunk.
120
+ */
121
+ reset () {
122
+ const peak = this.length
123
+
124
+ this.length = 0
125
+
126
+ if (this.buffer.length > this.#minSize && peak * SHRINK_USAGE_RATIO < this.buffer.length) {
127
+ if (++this.#lowUsageStreak >= SHRINK_AFTER_FLUSHES) {
128
+ const newSize = Math.max(this.#minSize, this.buffer.length >>> 1)
129
+ this.buffer = Buffer.allocUnsafe(newSize)
130
+ this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength)
131
+ this.#lowUsageStreak = 0
132
+ }
133
+ } else {
134
+ this.#lowUsageStreak = 0
135
+ }
136
+ }
137
+
138
+ writeNull () {
139
+ const offset = this.length
140
+
141
+ this.reserve(1)
142
+ this.buffer[offset] = 0xC0
143
+ }
144
+
145
+ /**
146
+ * @param {boolean} value
147
+ */
148
+ writeBoolean (value) {
149
+ const offset = this.length
150
+
151
+ this.reserve(1)
152
+ this.buffer[offset] = value ? 0xC3 : 0xC2
153
+ }
154
+
155
+ /**
156
+ * @param {number} size 0..15.
157
+ */
158
+ writeFixArray (size) {
159
+ const offset = this.length
160
+
161
+ this.reserve(1)
162
+ this.buffer[offset] = 0x90 + size
163
+ }
164
+
165
+ /**
166
+ * Reserve a 5-byte array32 header with `value.length` slots. Used when the
167
+ * length is not known to fit in fixarray.
168
+ *
169
+ * @param {{ length: number }} value
170
+ */
171
+ writeArrayPrefix (value) {
172
+ const length = value.length
173
+ const offset = this.length
174
+
175
+ this.reserve(5)
176
+ this.buffer[offset] = 0xDD
177
+ this.buffer[offset + 1] = length >> 24
178
+ this.buffer[offset + 2] = length >> 16
179
+ this.buffer[offset + 3] = length >> 8
180
+ this.buffer[offset + 4] = length
181
+ }
182
+
183
+ /**
184
+ * Reserve a 5-byte map32 header with `keysLength` entries.
185
+ *
186
+ * @param {number} keysLength
187
+ */
188
+ writeMapPrefix (keysLength) {
189
+ const offset = this.length
190
+
191
+ this.reserve(5)
192
+ this.buffer[offset] = 0xDF
193
+ this.buffer[offset + 1] = keysLength >> 24
194
+ this.buffer[offset + 2] = keysLength >> 16
195
+ this.buffer[offset + 3] = keysLength >> 8
196
+ this.buffer[offset + 4] = keysLength
197
+ }
198
+
199
+ /**
200
+ * Write a single raw byte. Used by `0.5.js` for the fixarray-of-twelve span
201
+ * marker.
202
+ *
203
+ * @param {number} value
204
+ */
205
+ writeByte (value) {
206
+ this.reserve(1)
207
+ this.buffer[this.length - 1] = value
208
+ }
209
+
210
+ /**
211
+ * @param {Buffer | Uint8Array} value
212
+ */
213
+ writeBin (value) {
214
+ const offset = this.length
215
+
216
+ if (value.byteLength < 256) {
217
+ this.reserve(2)
218
+ this.buffer[offset] = 0xC4
219
+ this.buffer[offset + 1] = value.byteLength
220
+ } else if (value.byteLength < 65_536) {
221
+ this.reserve(3)
222
+ this.buffer[offset] = 0xC5
223
+ this.buffer[offset + 1] = value.byteLength >> 8
224
+ this.buffer[offset + 2] = value.byteLength
225
+ } else {
226
+ this.reserve(5)
227
+ this.buffer[offset] = 0xC6
228
+ this.buffer[offset + 1] = value.byteLength >> 24
229
+ this.buffer[offset + 2] = value.byteLength >> 16
230
+ this.buffer[offset + 3] = value.byteLength >> 8
231
+ this.buffer[offset + 4] = value.byteLength
232
+ }
233
+
234
+ this.set(value)
235
+ }
236
+
237
+ /**
238
+ * Write `value` as msgpack uint32 (`0xCE` + 4 bytes), regardless of
239
+ * magnitude. Callers that want the shortest encoding should use `writeUint`.
240
+ *
241
+ * @param {number} value
242
+ */
243
+ writeInteger (value) {
244
+ const offset = this.length
245
+
246
+ this.reserve(5)
247
+ this.buffer[offset] = 0xCE
248
+ this.buffer[offset + 1] = value >> 24
249
+ this.buffer[offset + 2] = value >> 16
250
+ this.buffer[offset + 3] = value >> 8
251
+ this.buffer[offset + 4] = value
252
+ }
253
+
254
+ /**
255
+ * Write `value` as msgpack uint64 (`0xCF` + 8 bytes).
256
+ *
257
+ * @param {number} value
258
+ */
259
+ writeLong (value) {
260
+ const offset = this.length
261
+ const hi = (value / 2 ** 32) >> 0
262
+ const lo = value >>> 0
263
+
264
+ this.reserve(9)
265
+ this.buffer[offset] = 0xCF
266
+ this.buffer[offset + 1] = hi >> 24
267
+ this.buffer[offset + 2] = hi >> 16
268
+ this.buffer[offset + 3] = hi >> 8
269
+ this.buffer[offset + 4] = hi
270
+ this.buffer[offset + 5] = lo >> 24
271
+ this.buffer[offset + 6] = lo >> 16
272
+ this.buffer[offset + 7] = lo >> 8
273
+ this.buffer[offset + 8] = lo
274
+ }
275
+
276
+ /**
277
+ * Pick the shortest valid msgpack uint encoding for a non-negative integer.
278
+ *
279
+ * @param {number} value
280
+ */
281
+ writeUnsigned (value) {
282
+ const offset = this.length
283
+
284
+ if (value <= 0x7F) {
285
+ this.reserve(1)
286
+ this.buffer[offset] = value
287
+ } else if (value <= 0xFF) {
288
+ this.reserve(2)
289
+ this.buffer[offset] = 0xCC
290
+ this.buffer[offset + 1] = value
291
+ } else if (value <= 0xFF_FF) {
292
+ this.reserve(3)
293
+ this.buffer[offset] = 0xCD
294
+ this.buffer[offset + 1] = value >> 8
295
+ this.buffer[offset + 2] = value
296
+ } else if (value <= 0xFF_FF_FF_FF) {
297
+ this.reserve(5)
298
+ this.buffer[offset] = 0xCE
299
+ this.buffer[offset + 1] = value >> 24
300
+ this.buffer[offset + 2] = value >> 16
301
+ this.buffer[offset + 3] = value >> 8
302
+ this.buffer[offset + 4] = value
303
+ } else {
304
+ const hi = (value / 2 ** 32) >> 0
305
+ const lo = value >>> 0
306
+
307
+ this.reserve(9)
308
+ this.buffer[offset] = 0xCF
309
+ this.buffer[offset + 1] = hi >> 24
310
+ this.buffer[offset + 2] = hi >> 16
311
+ this.buffer[offset + 3] = hi >> 8
312
+ this.buffer[offset + 4] = hi
313
+ this.buffer[offset + 5] = lo >> 24
314
+ this.buffer[offset + 6] = lo >> 16
315
+ this.buffer[offset + 7] = lo >> 8
316
+ this.buffer[offset + 8] = lo
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Pick the shortest valid msgpack int encoding for a negative integer.
322
+ *
323
+ * @param {number} value
324
+ */
325
+ writeSigned (value) {
326
+ const offset = this.length
327
+
328
+ if (value >= -0x20) {
329
+ this.reserve(1)
330
+ this.buffer[offset] = value
331
+ } else if (value >= -0x80) {
332
+ this.reserve(2)
333
+ this.buffer[offset] = 0xD0
334
+ this.buffer[offset + 1] = value
335
+ } else if (value >= -0x80_00) {
336
+ this.reserve(3)
337
+ this.buffer[offset] = 0xD1
338
+ this.buffer[offset + 1] = value >> 8
339
+ this.buffer[offset + 2] = value
340
+ } else if (value >= -0x80_00_00_00) {
341
+ this.reserve(5)
342
+ this.buffer[offset] = 0xD2
343
+ this.buffer[offset + 1] = value >> 24
344
+ this.buffer[offset + 2] = value >> 16
345
+ this.buffer[offset + 3] = value >> 8
346
+ this.buffer[offset + 4] = value
347
+ } else {
348
+ const hi = Math.floor(value / 2 ** 32)
349
+ const lo = value >>> 0
350
+
351
+ this.reserve(9)
352
+ this.buffer[offset] = 0xD3
353
+ this.buffer[offset + 1] = hi >> 24
354
+ this.buffer[offset + 2] = hi >> 16
355
+ this.buffer[offset + 3] = hi >> 8
356
+ this.buffer[offset + 4] = hi
357
+ this.buffer[offset + 5] = lo >> 24
358
+ this.buffer[offset + 6] = lo >> 16
359
+ this.buffer[offset + 7] = lo >> 8
360
+ this.buffer[offset + 8] = lo
361
+ }
362
+ }
363
+
364
+ // TODO: Support BigInt larger than 64bit.
365
+ /**
366
+ * @param {bigint} value
367
+ */
368
+ writeBigInt (value) {
369
+ const offset = this.length
370
+
371
+ this.reserve(9)
372
+
373
+ if (value >= 0n) {
374
+ this.buffer[offset] = 0xCF
375
+ this.view.setBigUint64(offset + 1, value)
376
+ } else {
377
+ this.buffer[offset] = 0xD3
378
+ this.view.setBigInt64(offset + 1, value)
379
+ }
380
+ }
381
+
382
+ /**
383
+ * @param {number} value
384
+ */
385
+ writeFloat (value) {
386
+ const offset = this.length
387
+
388
+ this.reserve(9)
389
+ this.buffer[offset] = 0xCB
390
+ this.view.setFloat64(offset + 1, value)
391
+ }
392
+
393
+ /**
394
+ * Pick the shortest valid msgpack number encoding for `value`. `NaN`
395
+ * collapses to fixint 0 — callers that need to preserve `NaN` (the tracer's
396
+ * span numeric path) should use `writeIntOrFloat` instead.
397
+ *
398
+ * @param {number} value
399
+ */
400
+ writeNumber (value) {
401
+ if (Number.isNaN(value)) {
402
+ value = 0
403
+ }
404
+ if (Number.isInteger(value)) {
405
+ if (value >= 0) {
406
+ this.writeUnsigned(value)
407
+ } else {
408
+ this.writeSigned(value)
409
+ }
410
+ } else {
411
+ this.writeFloat(value)
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Emit `value` as the smallest valid msgpack number encoding: compact
417
+ * unsigned/signed int when integer, float64 otherwise. Unlike `writeNumber`,
418
+ * NaN keeps its float64 bits instead of coercing to fixint 0. Used on the
419
+ * tracer hot path so the agent sees the value the application produced.
420
+ *
421
+ * @param {number} value
422
+ */
423
+ writeIntOrFloat (value) {
424
+ // Fast path: positive fixint (0..127). `value === (value & 0x7F)` is true
425
+ // iff `value` is an exact integer in that range — covers `error: 0/1`,
426
+ // priority flags, attribute counts, HTTP status codes mapped to numbers,
427
+ // and most small metrics. NaN, ±Infinity, negatives, and any non-integer
428
+ // float fall through.
429
+ if (value === (value & 0x7F)) {
430
+ const offset = this.length
431
+ this.reserve(1)
432
+ this.buffer[offset] = value
433
+ return
434
+ }
435
+ if (Number.isInteger(value)) {
436
+ if (value >= 0) {
437
+ this.writeUnsigned(value)
438
+ } else {
439
+ this.writeSigned(value)
440
+ }
441
+ } else {
442
+ this.writeFloat(value)
443
+ }
444
+ }
445
+
62
446
  #resize (size) {
63
447
  const oldBuffer = this.buffer
64
448
 
65
449
  this.buffer = Buffer.allocUnsafe(size)
66
- this.view = new DataView(this.buffer.buffer)
450
+ this.view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength)
67
451
 
68
452
  oldBuffer.copy(this.buffer, 0, 0, this.length)
69
453
  }
@@ -1,6 +1,100 @@
1
1
  'use strict'
2
2
 
3
3
  const MsgpackChunk = require('./chunk')
4
- const { MsgpackEncoder } = require('./encoder')
5
4
 
6
- module.exports = { MsgpackChunk, MsgpackEncoder }
5
+ /**
6
+ * Encode an arbitrary JS value as a standalone msgpack buffer. Used by
7
+ * `DataStreamsWriter` (pipeline stats) where the payload shape is decided at
8
+ * runtime; encoder code that owns a `MsgpackChunk` should call
9
+ * `chunk.writeX(...)` directly instead.
10
+ *
11
+ * @param {unknown} value
12
+ * @returns {Buffer}
13
+ */
14
+ function encode (value) {
15
+ const bytes = new MsgpackChunk()
16
+ writeValue(bytes, value)
17
+
18
+ return bytes.buffer.subarray(0, bytes.length)
19
+ }
20
+
21
+ /**
22
+ * @param {unknown} value
23
+ * @returns {value is Record<string, unknown>}
24
+ */
25
+ function isPlainObject (value) {
26
+ return typeof value === 'object' && value !== null
27
+ }
28
+
29
+ /**
30
+ * @param {MsgpackChunk} bytes
31
+ * @param {unknown} value
32
+ */
33
+ function writeValue (bytes, value) {
34
+ switch (typeof value) {
35
+ case 'string':
36
+ bytes.write(value)
37
+ break
38
+ case 'number':
39
+ bytes.writeNumber(value)
40
+ break
41
+ case 'object':
42
+ if (value === null) {
43
+ bytes.writeNull()
44
+ } else if (Array.isArray(value)) {
45
+ writeArray(bytes, value)
46
+ } else if (Buffer.isBuffer(value)) {
47
+ bytes.writeBin(value)
48
+ } else if (ArrayBuffer.isView(value)) {
49
+ bytes.writeBin(/** @type {Uint8Array} */ (value))
50
+ } else if (isPlainObject(value)) {
51
+ writeMap(bytes, value)
52
+ }
53
+ break
54
+ case 'boolean':
55
+ bytes.writeBoolean(value)
56
+ break
57
+ case 'bigint':
58
+ bytes.writeBigInt(value)
59
+ break
60
+ case 'symbol':
61
+ bytes.write(value.toString())
62
+ break
63
+ default: // function, undefined
64
+ bytes.writeNull()
65
+ break
66
+ }
67
+ }
68
+
69
+ /**
70
+ * @param {MsgpackChunk} bytes
71
+ * @param {unknown[]} value
72
+ */
73
+ function writeArray (bytes, value) {
74
+ if (value.length < 16) {
75
+ bytes.writeFixArray(value.length)
76
+ } else {
77
+ bytes.writeArrayPrefix(value)
78
+ }
79
+
80
+ for (const item of value) {
81
+ writeValue(bytes, item)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @param {MsgpackChunk} bytes
87
+ * @param {Record<string, unknown>} value
88
+ */
89
+ function writeMap (bytes, value) {
90
+ const keys = Object.keys(value)
91
+
92
+ bytes.writeMapPrefix(keys.length)
93
+
94
+ for (const key of keys) {
95
+ bytes.write(key)
96
+ writeValue(bytes, value[key])
97
+ }
98
+ }
99
+
100
+ module.exports = { MsgpackChunk, encode }
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+
5
+ /**
6
+ * Encode a single value as a ULEB128 varint (variable-length integer).
7
+ * Uses 7 bits per byte, with MSB as continuation flag.
8
+ *
9
+ * @param {number} value - Non-negative integer to encode
10
+ * @returns {number[]} Array of bytes representing the varint
11
+ */
12
+ function encodeVarint (value) {
13
+ const bytes = []
14
+ while (value > 0x7F) {
15
+ bytes.push((value & 0x7F) | 0x80) // Set continuation bit
16
+ value >>>= 7
17
+ }
18
+ bytes.push(value & 0x7F) // Final byte without continuation bit
19
+ return bytes
20
+ }
21
+
22
+ /**
23
+ * Encode a set of serial IDs using delta-varint encoding.
24
+ *
25
+ * Algorithm:
26
+ * 1. Sort serial IDs in ascending order
27
+ * 2. Compute deltas from previous value (first delta = first value)
28
+ * 3. Encode each delta as varint
29
+ * 4. Base64 encode the result
30
+ *
31
+ * @param {Set<number>} serialIds - Set of serial IDs to encode
32
+ * @returns {string} Base64-encoded delta-varint string
33
+ */
34
+ function encodeDeltaVarint (serialIds) {
35
+ if (!serialIds || serialIds.size === 0) {
36
+ return ''
37
+ }
38
+
39
+ // Sort IDs in ascending order
40
+ const sorted = [...serialIds].sort((a, b) => a - b)
41
+
42
+ // Compute deltas and encode as varints
43
+ const bytes = []
44
+ let prev = 0
45
+
46
+ for (const id of sorted) {
47
+ const delta = id - prev
48
+ bytes.push(...encodeVarint(delta))
49
+ prev = id
50
+ }
51
+
52
+ // Base64 encode the byte array
53
+ return Buffer.from(bytes).toString('base64')
54
+ }
55
+
56
+ /**
57
+ * Hash a targeting key using SHA256.
58
+ *
59
+ * @param {string} targetingKey - The targeting key to hash
60
+ * @returns {string} Lowercase hex digest of the SHA256 hash
61
+ */
62
+ function hashTargetingKey (targetingKey) {
63
+ return crypto.createHash('sha256').update(targetingKey).digest('hex')
64
+ }
65
+
66
+ module.exports = {
67
+ encodeVarint,
68
+ encodeDeltaVarint,
69
+ hashTargetingKey,
70
+ }
@@ -5,12 +5,16 @@ const { channel } = require('dc-polyfill')
5
5
  const log = require('../log')
6
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
7
  const EvalMetricsHook = require('./eval-metrics-hook')
8
+ const SpanEnrichmentHook = require('./span-enrichment-hook')
8
9
 
9
10
  /**
10
11
  * OpenFeature provider that integrates with Datadog's feature flagging system.
11
12
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
12
13
  */
13
14
  class FlaggingProvider extends DatadogNodeServerProvider {
15
+ /** @type {SpanEnrichmentHook?} */
16
+ #spanEnrichmentHook
17
+
14
18
  /**
15
19
  * @param {import('../tracer')} tracer - Datadog tracer instance
16
20
  * @param {import('../config')} config - Tracer configuration object
@@ -27,10 +31,26 @@ class FlaggingProvider extends DatadogNodeServerProvider {
27
31
 
28
32
  this.hooks.push(new EvalMetricsHook(config))
29
33
 
34
+ if (config.experimental.flaggingProvider.spanEnrichment?.enabled) {
35
+ this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
36
+ this.hooks.push(this.#spanEnrichmentHook)
37
+ log.info('%s span enrichment enabled', this.constructor.name)
38
+ } else {
39
+ log.info('%s span enrichment disabled', this.constructor.name)
40
+ }
41
+
30
42
  log.debug('%s created with timeout: %dms', this.constructor.name,
31
43
  config.experimental.flaggingProvider.initializationTimeoutMs)
32
44
  }
33
45
 
46
+ /**
47
+ * Called when the provider is shut down.
48
+ * Cleans up resources including channel subscriptions.
49
+ */
50
+ onClose () {
51
+ this.#spanEnrichmentHook?.destroy()
52
+ }
53
+
34
54
  /**
35
55
  * Internal method to update flag configuration from Remote Config.
36
56
  * This method is called automatically when Remote Config delivers UFC updates.