dd-trace 5.103.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  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 +15 -2
  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/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -4,11 +4,9 @@ const zlib = require('zlib')
4
4
  const pkg = require('../../../../package.json')
5
5
  const log = require('../log')
6
6
  const request = require('../exporters/common/request')
7
- const { MsgpackEncoder } = require('../msgpack')
7
+ const { encode: encodeMsgpack } = require('../msgpack')
8
8
  const { getAgentUrl } = require('../agent/url')
9
9
 
10
- const msgpack = new MsgpackEncoder()
11
-
12
10
  function makeRequest (data, url, cb) {
13
11
  const options = {
14
12
  path: '/v0.1/pipeline_stats',
@@ -39,7 +37,7 @@ class DataStreamsWriter {
39
37
  log.debug('Maximum number of active requests reached. Payload discarded: %j', payload)
40
38
  return
41
39
  }
42
- const encodedPayload = msgpack.encode(payload)
40
+ const encodedPayload = encodeMsgpack(payload)
43
41
 
44
42
  zlib.gzip(encodedPayload, { level: 1 }, (err, compressedData) => {
45
43
  if (err) {
@@ -6,7 +6,7 @@ module.exports = {
6
6
  templateRequiresEvaluation,
7
7
  }
8
8
 
9
- const identifierRegex = /^[@a-zA-Z_$][\w$]*$/
9
+ const identifierRegex = /^(@[\w$]+|[a-zA-Z_$][\w$]*)$/
10
10
 
11
11
  // The following identifiers have purposefully not been included in this list:
12
12
  // - The reserved words `this` and `super` as they can have valid use cases as `ref` values
@@ -99,14 +99,11 @@ function compile (node) {
99
99
  ? `(typeof ${compile(value[0])} === '${value[1]}')` // TODO: Is parenthesizing necessary?
100
100
  : `Function.prototype[Symbol.hasInstance].call(${assertIdentifier(value[1])}, ${compile(value[0])})`
101
101
  } else if (type === 'ref') {
102
- if (value === '@it') {
103
- return '$dd_it'
104
- } else if (value === '@key') {
105
- return '$dd_key'
106
- } else if (value === '@value') {
107
- return '$dd_value'
102
+ const refValue = assertIdentifier(value)
103
+ if (refValue.startsWith('@')) {
104
+ return `$dd_${refValue.slice(1)}`
108
105
  }
109
- return assertIdentifier(value)
106
+ return refValue
110
107
  } else if (Array.isArray(value)) {
111
108
  const args = value.map(compile)
112
109
  switch (type) {
@@ -1,11 +1,17 @@
1
1
  'use strict'
2
2
 
3
3
  const getConfig = require('../config')
4
- const { MsgpackChunk, MsgpackEncoder } = require('../msgpack')
4
+ const { MsgpackChunk } = require('../msgpack')
5
5
  const log = require('../log')
6
6
  const { normalizeSpan } = require('./tags-processors')
7
7
 
8
8
  const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
9
+ // Values longer than this byte threshold skip the `_stringMap` lookup and
10
+ // emit through `bytes.write` directly. Hashing a multi-KiB string for
11
+ // `Map.get` costs more than the cache hit saves on the inputs that produce
12
+ // strings this long (events JSON, stack traces, large query bodies) — they
13
+ // are unique per span, so the cache hit rate stays near zero anyway.
14
+ const STRING_CACHE_BYPASS_LIMIT = 1024
9
15
 
10
16
  // Pre-encoded static keys + value-prefix bytes; the hot encode loop emits
11
17
  // each via one Uint8Array.set instead of routing through the string cache.
@@ -43,6 +49,17 @@ const KEY_SERVICE = buildKey('service')
43
49
  const KEY_ERROR = buildKey('error')
44
50
  const KEY_START = buildKey('start')
45
51
  const KEY_DURATION = buildKey('duration')
52
+
53
+ // Fused `[KEY_ERROR, fixint]` payloads. `error` is `0` or `1` on nearly every
54
+ // span (the boolean-shaped tracer field collapsed onto a single byte). One
55
+ // `bytes.set` writes the key and the value together instead of routing the
56
+ // value through `writeIntOrFloat`'s reserve + branch table.
57
+ const KEY_ERROR_0 = Buffer.concat([KEY_ERROR, Buffer.from([0x00])])
58
+ const KEY_ERROR_1 = Buffer.concat([KEY_ERROR, Buffer.from([0x01])])
59
+ // `[KEY_START, 0xCF]` — `start` is always a nanosecond timestamp ≥ 2³², so
60
+ // the msgpack u64 type byte is statically known and fuses with the key. The
61
+ // 8-byte value is written inline right after.
62
+ const KEY_START_PREFIX = buildKeyWithPrefix('start', 0xCF)
46
63
  const KEY_SPAN_EVENTS = buildKey('span_events')
47
64
  const KEY_META_STRUCT = buildKey('meta_struct')
48
65
  const KEY_TRACE_ID_PREFIX = buildKeyWithPrefix('trace_id', 0xCF)
@@ -97,6 +114,8 @@ const ATTR_PAYLOAD_BOOL_FALSE = Buffer.concat([ATTR_PREFIX_BOOL, Buffer.from([0x
97
114
  function formatSpanWithLegacyEvents (span) {
98
115
  span = normalizeSpan(span)
99
116
  if (span.span_events) {
117
+ // TODO: this is currently a main cost driver. By unifying it with the formatter
118
+ // it should be possible to improve performance significantly overall.
100
119
  span.meta.events = stringifySpanEvents(span.span_events)
101
120
  // `= undefined` over `delete` to keep the span's hidden class — `delete`
102
121
  // would push every event-bearing span into V8 dictionary mode.
@@ -201,8 +220,12 @@ function escapeJsonString (value) {
201
220
  return '"' + value + '"'
202
221
  }
203
222
 
223
+ function lazyEncodedTraceBufferLogger (bytes, start, end) {
224
+ const hex = bytes.buffer.subarray(start, end).toString('hex').match(/../g).join(' ')
225
+ return `Adding encoded trace to buffer: ${hex}`
226
+ }
227
+
204
228
  class AgentEncoder {
205
- #msgpack = new MsgpackEncoder()
206
229
  #limit
207
230
  #writer
208
231
  #config
@@ -239,11 +262,7 @@ class AgentEncoder {
239
262
 
240
263
  if (this.#debugEncoding) {
241
264
  const end = bytes.length
242
- // eslint-disable-next-line eslint-rules/eslint-log-printf-style
243
- log.debug(() => {
244
- const hex = bytes.buffer.subarray(start, end).toString('hex').match(/../g).join(' ')
245
- return `Adding encoded trace to buffer: ${hex}`
246
- })
265
+ log.debug(lazyEncodedTraceBufferLogger, bytes, start, end)
247
266
  }
248
267
 
249
268
  // Soft limit overshoot is fine — the agent caps at 50 MB.
@@ -269,7 +288,7 @@ class AgentEncoder {
269
288
  }
270
289
 
271
290
  _encode (bytes, trace) {
272
- this._encodeArrayPrefix(bytes, trace)
291
+ bytes.writeArrayPrefix(trace)
273
292
 
274
293
  const formatSpan = this.#formatSpan
275
294
  const stringMap = this._stringMap
@@ -286,10 +305,10 @@ class AgentEncoder {
286
305
  if (span.span_events) mapSize++
287
306
 
288
307
  // Pre-fetch the cached string entries up front and fuse the map prefix,
289
- // optional `type`, three IDs, and `name` / `resource` / `service`
308
+ // optional `type`, three IDs, `name` / `resource` / `service`, and —
309
+ // in the common fixint-error case — the error/start/duration_key
290
310
  // emissions into a single `bytes.reserve` + sequential native writes.
291
- // Replaces seven `bytes.reserve` calls per span (one each for the
292
- // header, type, three IDs, three strings) with one.
311
+ // Replaces up to ten separate `bytes.reserve` calls per span with one.
293
312
  let typeEntry
294
313
  if (span.type) {
295
314
  typeEntry = stringMap[span.type] ?? this._cacheString(span.type)
@@ -301,8 +320,17 @@ class AgentEncoder {
301
320
  const resourceLen = resourceEntry.length
302
321
  const serviceLen = serviceEntry.length
303
322
 
304
- // 1 byte map prefix + 3 ID fields (10/9/11 bytes prefix + 8 bytes value
305
- // each) + the three string fields.
323
+ // Almost every span carries `error: 0` or `error: 1` AND a nanosecond
324
+ // `start` timestamp 2³² (so `start` always encodes as a u64). When
325
+ // both hold, the block fuses error key+value, the start key + 0xCF
326
+ // type byte + 8-byte timestamp, and the duration key into the per-span
327
+ // reserve. The fallback path covers synthetic/test inputs with small
328
+ // starts and rare non-binary error flags by keeping per-field emits so
329
+ // each integer picks the shortest msgpack encoding.
330
+ const errorIsFixint = span.error === 0 || span.error === 1
331
+ const startFitsU64 = span.start >= 0x1_00_00_00_00
332
+ const fuseTail = errorIsFixint && startFitsU64
333
+
306
334
  let blockSize = 1 +
307
335
  KEY_TRACE_ID_PREFIX.length + 8 +
308
336
  KEY_SPAN_ID_PREFIX.length + 8 +
@@ -311,6 +339,9 @@ class AgentEncoder {
311
339
  KEY_RESOURCE.length + resourceLen +
312
340
  KEY_SERVICE.length + serviceLen
313
341
  if (typeEntry) blockSize += KEY_TYPE.length + typeEntry.length
342
+ if (fuseTail) {
343
+ blockSize += KEY_ERROR_0.length + KEY_START_PREFIX.length + 8 + KEY_DURATION.length
344
+ }
314
345
 
315
346
  const blockOffset = bytes.length
316
347
  bytes.reserve(blockSize)
@@ -343,13 +374,35 @@ class AgentEncoder {
343
374
  target.set(KEY_SERVICE, cursor)
344
375
  cursor += KEY_SERVICE.length
345
376
  target.set(serviceEntry, cursor)
377
+ cursor += serviceLen
378
+
379
+ if (fuseTail) {
380
+ target.set(span.error === 0 ? KEY_ERROR_0 : KEY_ERROR_1, cursor)
381
+ cursor += KEY_ERROR_0.length
346
382
 
347
- bytes.set(KEY_ERROR)
348
- this._encodeIntOrFloat(bytes, span.error)
349
- bytes.set(KEY_START)
350
- this._encodeIntOrFloat(bytes, span.start)
351
- bytes.set(KEY_DURATION)
352
- this._encodeIntOrFloat(bytes, span.duration)
383
+ target.set(KEY_START_PREFIX, cursor)
384
+ cursor += KEY_START_PREFIX.length
385
+ // Inline u64 write so the 0xCF type byte and the 8 timestamp bytes
386
+ // share the same reserve as the keys.
387
+ target.writeUInt32BE((span.start / 0x1_00_00_00_00) >>> 0, cursor)
388
+ target.writeUInt32BE(span.start >>> 0, cursor + 4)
389
+ cursor += 8
390
+
391
+ target.set(KEY_DURATION, cursor)
392
+ } else {
393
+ if (span.error === 0) {
394
+ bytes.set(KEY_ERROR_0)
395
+ } else if (span.error === 1) {
396
+ bytes.set(KEY_ERROR_1)
397
+ } else {
398
+ bytes.set(KEY_ERROR)
399
+ bytes.writeIntOrFloat(span.error)
400
+ }
401
+ bytes.set(KEY_START)
402
+ bytes.writeIntOrFloat(span.start)
403
+ bytes.set(KEY_DURATION)
404
+ }
405
+ bytes.writeIntOrFloat(span.duration)
353
406
 
354
407
  this.#encodeMetaEntries(bytes, KEY_META_PREFIX, span.meta)
355
408
  this.#encodeMetaEntries(bytes, KEY_METRICS_PREFIX, span.metrics)
@@ -390,34 +443,14 @@ class AgentEncoder {
390
443
 
391
444
  _reset () {
392
445
  this._traceCount = 0
393
- this._traceBytes.length = 0
446
+ this._traceBytes.reset()
394
447
  this._stringCount = 0
395
- this._stringBytes.length = 0
448
+ this._stringBytes.reset()
396
449
  this._stringMap = Object.create(null)
397
450
 
398
451
  this._cacheString('')
399
452
  }
400
453
 
401
- _encodeBuffer (bytes, buffer) {
402
- this.#msgpack.encodeBin(bytes, buffer)
403
- }
404
-
405
- _encodeBool (bytes, value) {
406
- this.#msgpack.encodeBoolean(bytes, value)
407
- }
408
-
409
- _encodeArrayPrefix (bytes, value) {
410
- this.#msgpack.encodeArrayPrefix(bytes, value)
411
- }
412
-
413
- _encodeMapPrefix (bytes, keysLength) {
414
- this.#msgpack.encodeMapPrefix(bytes, keysLength)
415
- }
416
-
417
- _encodeByte (bytes, value) {
418
- this.#msgpack.encodeByte(bytes, value)
419
- }
420
-
421
454
  // TODO: Use BigInt instead.
422
455
  _encodeId (bytes, identifier) {
423
456
  const idBuffer = identifier.toBuffer()
@@ -438,18 +471,6 @@ class AgentEncoder {
438
471
  target[offset + 8] = idBuffer[start + 7]
439
472
  }
440
473
 
441
- _encodeNumber (bytes, value) {
442
- this.#msgpack.encodeNumber(bytes, value)
443
- }
444
-
445
- _encodeInteger (bytes, value) {
446
- this.#msgpack.encodeInteger(bytes, value)
447
- }
448
-
449
- _encodeLong (bytes, value) {
450
- this.#msgpack.encodeLong(bytes, value)
451
- }
452
-
453
474
  // Single pass: reserve the count slot, encode entries while counting, patch the count.
454
475
  // Subclasses (0.5, CI visibility encoders) inherit this; the wire stays on float64
455
476
  // for numeric values to keep their established trace / events intake unchanged.
@@ -467,7 +488,7 @@ class AgentEncoder {
467
488
  count++
468
489
  } else if (typeof entryValue === 'number') {
469
490
  this._encodeString(bytes, key)
470
- this.#encodeFloat(bytes, entryValue)
491
+ bytes.writeFloat(entryValue)
471
492
  count++
472
493
  }
473
494
  }
@@ -480,6 +501,10 @@ class AgentEncoder {
480
501
  }
481
502
 
482
503
  _encodeString (bytes, value = '') {
504
+ if (value.length > STRING_CACHE_BYPASS_LIMIT) {
505
+ bytes.write(value)
506
+ return
507
+ }
483
508
  const entry = this._stringMap[value] ?? this._cacheString(value)
484
509
  const length = entry.length
485
510
  const offset = bytes.length
@@ -540,6 +565,17 @@ class AgentEncoder {
540
565
  const writeOffset = bytes.length
541
566
 
542
567
  if (typeof entryValue === 'string') {
568
+ if (entryValue.length > STRING_CACHE_BYPASS_LIMIT) {
569
+ // Long values (events JSON, stack traces, large query bodies) are
570
+ // unique per span; hashing them for the cache lookup costs more
571
+ // than the lookup ever recovers. Emit the key from the cache and
572
+ // stream the value directly.
573
+ bytes.reserve(keyEntryLen)
574
+ bytes.buffer.set(keyEntry, writeOffset)
575
+ bytes.write(entryValue)
576
+ count++
577
+ continue
578
+ }
543
579
  const valueEntry = stringMap[entryValue] ?? this._cacheString(entryValue)
544
580
  const valueEntryLen = valueEntry.length
545
581
  bytes.reserve(keyEntryLen + valueEntryLen)
@@ -547,9 +583,22 @@ class AgentEncoder {
547
583
  target.set(keyEntry, writeOffset)
548
584
  target.set(valueEntry, writeOffset + keyEntryLen)
549
585
  } else {
550
- bytes.reserve(keyEntryLen)
551
- bytes.buffer.set(keyEntry, writeOffset)
552
- this._encodeIntOrFloat(bytes, entryValue)
586
+ // Speculate that `entryValue` is a positive fixint (0..127): one
587
+ // reserve covers both the key and the value. The metrics map (sample
588
+ // rate, priority, `_dd.measured`, attribute counts) is mostly small
589
+ // unsigned integers, so the speculation wins on every entry that
590
+ // doesn't go through the slow `writeIntOrFloat` dispatch chain.
591
+ bytes.reserve(keyEntryLen + 1)
592
+ const target = bytes.buffer
593
+ target.set(keyEntry, writeOffset)
594
+ if (entryValue === (entryValue & 0x7F)) {
595
+ target[writeOffset + keyEntryLen] = entryValue
596
+ } else {
597
+ // Speculation missed; rewind the speculative byte and route the
598
+ // value through the full encoder so it picks the right type.
599
+ bytes.length = writeOffset + keyEntryLen
600
+ bytes.writeIntOrFloat(entryValue)
601
+ }
553
602
  }
554
603
  count++
555
604
  }
@@ -589,41 +638,6 @@ class AgentEncoder {
589
638
  return offset + 8
590
639
  }
591
640
 
592
- /**
593
- * Emit `value` as the smallest valid msgpack number encoding: compact
594
- * unsigned/signed int when integer, float64 otherwise. Unlike
595
- * `MsgpackEncoder#encodeNumber`, NaN keeps its float64 bits instead of
596
- * coercing to fixint 0.
597
- *
598
- * Underscore-protected so the 0.5 subclass can call it from its own
599
- * `_encode` / `_encodeMap` overrides.
600
- *
601
- * @param {MsgpackChunk} bytes
602
- * @param {number} value
603
- */
604
- _encodeIntOrFloat (bytes, value) {
605
- // Fast path: positive fixint (0..127). `value === (value & 0x7F)` is true
606
- // iff `value` is an exact integer in that range — covers `error: 0/1`,
607
- // priority flags, attribute counts, HTTP status codes mapped to numbers,
608
- // and most small metrics. NaN, ±Infinity, negatives, and any non-integer
609
- // float fall through.
610
- if (value === (value & 0x7F)) {
611
- const offset = bytes.length
612
- bytes.reserve(1)
613
- bytes.buffer[offset] = value
614
- return
615
- }
616
- if (Number.isInteger(value)) {
617
- if (value >= 0) {
618
- this.#msgpack.encodeUnsigned(bytes, value)
619
- } else {
620
- this.#msgpack.encodeSigned(bytes, value)
621
- }
622
- } else {
623
- this.#encodeFloat(bytes, value)
624
- }
625
- }
626
-
627
641
  /**
628
642
  * @param {MsgpackChunk} bytes
629
643
  * @param {string | number | boolean} value
@@ -634,21 +648,17 @@ class AgentEncoder {
634
648
  this._encodeString(bytes, value)
635
649
  break
636
650
  case 'number':
637
- this.#encodeFloat(bytes, value)
651
+ bytes.writeFloat(value)
638
652
  break
639
653
  case 'boolean':
640
- this._encodeBool(bytes, value)
654
+ bytes.writeBoolean(value)
641
655
  break
642
656
  }
643
657
  }
644
658
 
645
- #encodeFloat (bytes, value) {
646
- this.#msgpack.encodeFloat(bytes, value)
647
- }
648
-
649
659
  #encodeMetaStruct (bytes, value) {
650
660
  if (Array.isArray(value)) {
651
- this._encodeMapPrefix(bytes, 0)
661
+ bytes.writeMapPrefix(0)
652
662
  return
653
663
  }
654
664
 
@@ -774,7 +784,7 @@ class AgentEncoder {
774
784
  bytes.set(KEY_NAME)
775
785
  this._encodeString(bytes, event.name)
776
786
  bytes.set(KEY_EVENT_TIME)
777
- this.#encodeFloat(bytes, event.time_unix_nano)
787
+ bytes.writeFloat(event.time_unix_nano)
778
788
 
779
789
  const attributes = event.attributes
780
790
  if (attributes !== null && typeof attributes === 'object') {
@@ -844,7 +854,7 @@ class AgentEncoder {
844
854
  if (typeof value === 'number') {
845
855
  this._encodeString(bytes, key)
846
856
  bytes.set(Number.isInteger(value) ? ATTR_PREFIX_INT : ATTR_PREFIX_DOUBLE)
847
- this._encodeIntOrFloat(bytes, value)
857
+ bytes.writeIntOrFloat(value)
848
858
  return true
849
859
  }
850
860
  if (typeof value === 'boolean') {
@@ -855,8 +865,11 @@ class AgentEncoder {
855
865
  if (Array.isArray(value)) {
856
866
  return this.#emitArrayAttribute(bytes, key, value)
857
867
  }
858
- memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
859
- `${key}: with value: ${typeof value}. Skipping encoding of pair.`
868
+ memoizedLogDebug(
869
+ key,
870
+ 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
871
+ '%s: with value: %s. Skipping encoding of pair.',
872
+ value
860
873
  )
861
874
  return false
862
875
  }
@@ -914,7 +927,7 @@ class AgentEncoder {
914
927
  }
915
928
  if (typeof value === 'number') {
916
929
  bytes.set(Number.isInteger(value) ? ATTR_PREFIX_INT : ATTR_PREFIX_DOUBLE)
917
- this._encodeIntOrFloat(bytes, value)
930
+ bytes.writeIntOrFloat(value)
918
931
  return true
919
932
  }
920
933
  if (typeof value === 'boolean') {
@@ -922,8 +935,11 @@ class AgentEncoder {
922
935
  return true
923
936
  }
924
937
  if (Array.isArray(value)) {
925
- memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
926
- `Skipping encoding key: ${key}: with value: ${typeof value}.`
938
+ memoizedLogDebug(
939
+ key,
940
+ 'Encountered nested array data type for span event v0.4 encoding. ' +
941
+ 'Skipping encoding key: %s: with value: %s.',
942
+ value
927
943
  )
928
944
  }
929
945
  return false
@@ -931,10 +947,10 @@ class AgentEncoder {
931
947
  }
932
948
 
933
949
  const seenKeys = new Set()
934
- function memoizedLogDebug (key, message) {
950
+ function memoizedLogDebug (key, message, value) {
935
951
  if (!seenKeys.has(key)) {
936
952
  seenKeys.add(key)
937
- log.debug(message)
953
+ log.debug(message, key, typeof value)
938
954
  }
939
955
  }
940
956
 
@@ -6,10 +6,18 @@ const { AgentEncoder: BaseEncoder, stringifySpanEvents } = require('./0.4')
6
6
  const ARRAY_OF_TWO = 0x92
7
7
  const ARRAY_OF_TWELVE = 0x9C
8
8
 
9
+ // Per-span fused head: `[0x9C, service-idx, name-idx, resource-idx,
10
+ // trace-id, span-id, parent-id]` — three uint32 indexes (5 bytes each) +
11
+ // three uint64 IDs (9 bytes each) + the array marker. Replaces seven
12
+ // separate reserves (`writeByte` + 3 × `writeInteger` + 3 × `_encodeId`)
13
+ // with one block-sized reserve per span.
14
+ const HEAD_BLOCK_SIZE = 1 + 5 * 3 + 9 * 3
15
+
9
16
  function formatSpan (span) {
10
17
  span = normalizeSpan(span)
11
18
  // v0.5 has no native span_events slot; always serialize as a meta tag.
12
19
  if (span.span_events) {
20
+ // TODO: this is a costly operation. Consolidate this with the formatter
13
21
  span.meta.events = stringifySpanEvents(span.span_events)
14
22
  // `= undefined` over `delete` to keep the span's hidden class.
15
23
  span.span_events = undefined
@@ -35,20 +43,36 @@ class AgentEncoder extends BaseEncoder {
35
43
  }
36
44
 
37
45
  _encode (bytes, trace) {
38
- this._encodeArrayPrefix(bytes, trace)
46
+ bytes.writeArrayPrefix(trace)
47
+
48
+ const stringMap = this._stringMap
39
49
 
40
50
  for (let span of trace) {
41
51
  span = formatSpan(span)
42
- this._encodeByte(bytes, ARRAY_OF_TWELVE)
43
- this._encodeString(bytes, span.service)
44
- this._encodeString(bytes, span.name)
45
- this._encodeString(bytes, span.resource)
46
- this._encodeId(bytes, span.trace_id)
47
- this._encodeId(bytes, span.span_id)
48
- this._encodeId(bytes, span.parent_id)
49
- this._encodeIntOrFloat(bytes, span.start || 0)
50
- this._encodeIntOrFloat(bytes, span.duration || 0)
51
- this._encodeIntOrFloat(bytes, span.error)
52
+
53
+ // Resolve the three head string indices up front. `_cacheString`
54
+ // writes into `_stringBytes`, an independent chunk, so the side
55
+ // effect is safe to interleave with the `_traceBytes` reserve
56
+ // below.
57
+ const serviceIndex = stringMap[span.service] ?? this._cacheString(span.service)
58
+ const nameIndex = stringMap[span.name] ?? this._cacheString(span.name)
59
+ const resourceIndex = stringMap[span.resource] ?? this._cacheString(span.resource)
60
+
61
+ const blockOffset = bytes.length
62
+ bytes.reserve(HEAD_BLOCK_SIZE)
63
+ const target = bytes.buffer
64
+
65
+ target[blockOffset] = ARRAY_OF_TWELVE
66
+ let cursor = this.#writeIndexAt(target, blockOffset + 1, serviceIndex)
67
+ cursor = this.#writeIndexAt(target, cursor, nameIndex)
68
+ cursor = this.#writeIndexAt(target, cursor, resourceIndex)
69
+ cursor = this.#writeIdAt(target, cursor, span.trace_id)
70
+ cursor = this.#writeIdAt(target, cursor, span.span_id)
71
+ this.#writeIdAt(target, cursor, span.parent_id)
72
+
73
+ bytes.writeIntOrFloat(span.start || 0)
74
+ bytes.writeIntOrFloat(span.duration || 0)
75
+ bytes.writeIntOrFloat(span.error)
52
76
  this._encodeMap(bytes, span.meta || {})
53
77
  this._encodeMap(bytes, span.metrics || {})
54
78
  this._encodeString(bytes, span.type)
@@ -65,18 +89,41 @@ class AgentEncoder extends BaseEncoder {
65
89
  bytes.reserve(5)
66
90
  bytes.buffer[offset] = 0xDF
67
91
 
92
+ const stringMap = this._stringMap
68
93
  let count = 0
69
94
  for (const key of Object.keys(value)) {
70
95
  const entryValue = value[key]
96
+ if (typeof entryValue !== 'string' && typeof entryValue !== 'number') continue
97
+
98
+ const keyIndex = stringMap[key] ?? this._cacheString(key)
99
+ const writeOffset = bytes.length
100
+
71
101
  if (typeof entryValue === 'string') {
72
- this._encodeString(bytes, key)
73
- this._encodeString(bytes, entryValue)
74
- count++
75
- } else if (typeof entryValue === 'number') {
76
- this._encodeString(bytes, key)
77
- this._encodeIntOrFloat(bytes, entryValue)
78
- count++
102
+ // Both halves are uint32 indices on the v0.5 wire — known
103
+ // size, so the key and value pair fuses into one reserve.
104
+ const valueIndex = stringMap[entryValue] ?? this._cacheString(entryValue)
105
+ bytes.reserve(10)
106
+ const target = bytes.buffer
107
+ this.#writeIndexAt(target, writeOffset, keyIndex)
108
+ this.#writeIndexAt(target, writeOffset + 5, valueIndex)
109
+ } else {
110
+ // Speculate that the value is a positive fixint (0..127). The
111
+ // metrics map is mostly small unsigned integers (sample priority,
112
+ // `_dd.measured`, attribute counts), so one reserve covers the
113
+ // key (5 bytes) and the value (1 byte). Misses rewind the
114
+ // speculative value byte and route the value through the full
115
+ // encoder so the wire still picks the shortest valid encoding.
116
+ bytes.reserve(6)
117
+ const target = bytes.buffer
118
+ this.#writeIndexAt(target, writeOffset, keyIndex)
119
+ if (entryValue === (entryValue & 0x7F)) {
120
+ target[writeOffset + 5] = entryValue
121
+ } else {
122
+ bytes.length = writeOffset + 5
123
+ bytes.writeIntOrFloat(entryValue)
124
+ }
79
125
  }
126
+ count++
80
127
  }
81
128
 
82
129
  const target = bytes.buffer
@@ -87,20 +134,18 @@ class AgentEncoder extends BaseEncoder {
87
134
  }
88
135
 
89
136
  _encodeString (bytes, value = '') {
137
+ const index = this._stringMap[value] ?? this._cacheString(value)
138
+ bytes.writeInteger(index)
139
+ }
140
+
141
+ _cacheString (value) {
90
142
  let index = this._stringMap[value]
91
143
  if (index === undefined) {
92
144
  index = this._stringCount++
93
145
  this._stringMap[value] = index
94
146
  this._stringBytes.write(value)
95
147
  }
96
- this._encodeInteger(bytes, index)
97
- }
98
-
99
- _cacheString (value) {
100
- if (this._stringMap[value] === undefined) {
101
- this._stringMap[value] = this._stringCount++
102
- this._stringBytes.write(value)
103
- }
148
+ return index
104
149
  }
105
150
 
106
151
  _writeStrings (buffer, offset) {
@@ -109,6 +154,49 @@ class AgentEncoder extends BaseEncoder {
109
154
 
110
155
  return offset
111
156
  }
157
+
158
+ /**
159
+ * Write `[0xCE, uint32(index)]` into `target` at `offset` and return the
160
+ * new cursor. Caller is responsible for having reserved enough room.
161
+ *
162
+ * @param {Uint8Array} target
163
+ * @param {number} offset
164
+ * @param {number} index
165
+ * @returns {number}
166
+ */
167
+ #writeIndexAt (target, offset, index) {
168
+ target[offset] = 0xCE
169
+ target[offset + 1] = index >> 24
170
+ target[offset + 2] = index >> 16
171
+ target[offset + 3] = index >> 8
172
+ target[offset + 4] = index
173
+ return offset + 5
174
+ }
175
+
176
+ /**
177
+ * Write `[0xCF, uint64(id)]` into `target` at `offset` and return the
178
+ * new cursor. The id is truncated to the low 8 bytes, matching the
179
+ * inherited `_encodeId` behavior.
180
+ *
181
+ * @param {Uint8Array} target
182
+ * @param {number} offset
183
+ * @param {{ toBuffer: () => Uint8Array | number[] }} identifier
184
+ * @returns {number}
185
+ */
186
+ #writeIdAt (target, offset, identifier) {
187
+ target[offset] = 0xCF
188
+ const idBuffer = identifier.toBuffer()
189
+ const start = idBuffer.length - 8
190
+ target[offset + 1] = idBuffer[start]
191
+ target[offset + 2] = idBuffer[start + 1]
192
+ target[offset + 3] = idBuffer[start + 2]
193
+ target[offset + 4] = idBuffer[start + 3]
194
+ target[offset + 5] = idBuffer[start + 4]
195
+ target[offset + 6] = idBuffer[start + 5]
196
+ target[offset + 7] = idBuffer[start + 6]
197
+ target[offset + 8] = idBuffer[start + 7]
198
+ return offset + 9
199
+ }
112
200
  }
113
201
 
114
202
  module.exports = { AgentEncoder }