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,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { writeFileSync } = require('node:fs')
3
+ const { existsSync, readFileSync, writeFileSync } = require('node:fs')
4
4
  const { tmpdir } = require('node:os')
5
5
  const { randomUUID } = require('node:crypto')
6
6
  const path = require('node:path')
@@ -8,6 +8,9 @@ const path = require('node:path')
8
8
  const { getValueFromEnvSources } = require('../config/helper')
9
9
  const log = require('../log')
10
10
 
11
+ const COVERAGE_BACKFILL_KEY = '_ddCoverageBackfill'
12
+ const COVERAGE_BACKFILL_ROOT_DIR_KEY = '_ddCoverageBackfillRootDir'
13
+
11
14
  /**
12
15
  * Gets the test optimization settings cache file path from the env var.
13
16
  * @returns {string|undefined} The cache file path, or undefined if not set.
@@ -36,26 +39,87 @@ function setupSettingsCachePath () {
36
39
  }
37
40
 
38
41
  /**
39
- * Writes the settings to the cache file specified by DD_EXPERIMENTAL_TEST_OPT_SETTINGS_CACHE.
40
- * Does nothing if the env var is not set.
41
- * @param {object} settings - The settings object to cache.
42
+ * Reads the shared test optimization cache file.
43
+ * @returns {object} Cached settings and metadata.
42
44
  */
43
- function writeSettingsToCache (settings) {
45
+ function readCacheFile () {
46
+ const settingsCachePath = getSettingsCachePath()
47
+ if (!settingsCachePath || !existsSync(settingsCachePath)) {
48
+ return {}
49
+ }
50
+
51
+ try {
52
+ return JSON.parse(readFileSync(settingsCachePath, 'utf8'))
53
+ } catch (err) {
54
+ log.debug('Failed to read settings cache: %s', err.message)
55
+ return {}
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Writes the shared test optimization cache file.
61
+ * @param {object} cache - Cached settings and metadata.
62
+ */
63
+ function writeCacheFile (cache) {
44
64
  const settingsCachePath = getSettingsCachePath()
45
65
  if (!settingsCachePath) {
46
66
  return
47
67
  }
48
68
 
49
69
  try {
50
- writeFileSync(settingsCachePath, JSON.stringify(settings), 'utf8')
70
+ writeFileSync(settingsCachePath, JSON.stringify(cache), 'utf8')
51
71
  log.debug('Settings written to %s', settingsCachePath)
52
72
  } catch (err) {
53
73
  log.error('Failed to write settings to cache file', err)
54
74
  }
55
75
  }
56
76
 
77
+ /**
78
+ * Writes the settings to the cache file specified by DD_EXPERIMENTAL_TEST_OPT_SETTINGS_CACHE.
79
+ * Does nothing if the env var is not set.
80
+ * @param {object} settings - The settings object to cache.
81
+ */
82
+ function writeSettingsToCache (settings) {
83
+ writeCacheFile({
84
+ ...readCacheFile(),
85
+ ...settings,
86
+ })
87
+ }
88
+
89
+ /**
90
+ * Writes TIA coverage backfill to the shared nyc settings cache.
91
+ * @param {object} coverage - Repository-relative coverage bitmaps by filename.
92
+ * @param {string} [rootDir] - Root directory that coverage filenames are relative to.
93
+ */
94
+ function writeCoverageBackfillToCache (coverage, rootDir) {
95
+ writeCacheFile({
96
+ ...readCacheFile(),
97
+ [COVERAGE_BACKFILL_KEY]: coverage,
98
+ [COVERAGE_BACKFILL_ROOT_DIR_KEY]: rootDir,
99
+ })
100
+ }
101
+
102
+ /**
103
+ * Reads TIA coverage backfill from the shared nyc settings cache.
104
+ * @returns {object|undefined} Repository-relative coverage bitmaps by filename.
105
+ */
106
+ function readCoverageBackfillFromCache () {
107
+ return readCacheFile()[COVERAGE_BACKFILL_KEY]
108
+ }
109
+
110
+ /**
111
+ * Reads TIA coverage backfill root directory from the shared nyc settings cache.
112
+ * @returns {string|undefined} Root directory that cached coverage filenames are relative to.
113
+ */
114
+ function readCoverageBackfillRootDirFromCache () {
115
+ return readCacheFile()[COVERAGE_BACKFILL_ROOT_DIR_KEY]
116
+ }
117
+
57
118
  module.exports = {
58
119
  getSettingsCachePath,
120
+ readCoverageBackfillFromCache,
121
+ readCoverageBackfillRootDirFromCache,
59
122
  setupSettingsCachePath,
123
+ writeCoverageBackfillToCache,
60
124
  writeSettingsToCache,
61
125
  }
@@ -65,7 +65,7 @@ export interface GeneratedConfig {
65
65
  dbm: {
66
66
  injectSqlBaseHash: boolean;
67
67
  };
68
- dbmPropagationMode: string;
68
+ dbmPropagationMode: "disabled" | "service" | "full" | "dynamic_service";
69
69
  DD_ACTION_EXECUTION_ID: string | undefined;
70
70
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED: boolean;
71
71
  DD_AGENTLESS_LOG_SUBMISSION_URL: string | undefined;
@@ -162,7 +162,6 @@ export interface GeneratedConfig {
162
162
  DD_TEST_FLEET_CONFIG_PATH: string | undefined;
163
163
  DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
164
164
  DD_TEST_SESSION_NAME: string | undefined;
165
- DD_TEST_TIA_KEEP_COV_CONFIG: boolean;
166
165
  DD_TRACE_AEROSPIKE_ENABLED: boolean;
167
166
  DD_TRACE_AI_ENABLED: boolean;
168
167
  DD_TRACE_AMQP10_ENABLED: boolean;
@@ -211,6 +210,7 @@ export interface GeneratedConfig {
211
210
  DD_TRACE_AWS_SDK_STEPFUNCTIONS_BATCH_PROPAGATION_ENABLED: boolean;
212
211
  DD_TRACE_AWS_SDK_STEPFUNCTIONS_ENABLED: boolean;
213
212
  DD_TRACE_AXIOS_ENABLED: boolean;
213
+ DD_TRACE_AZURE_COSMOS_ENABLED: boolean;
214
214
  DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED: boolean;
215
215
  DD_TRACE_AZURE_EVENT_HUBS_ENABLED: boolean;
216
216
  DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED: boolean;
@@ -331,6 +331,7 @@ export interface GeneratedConfig {
331
331
  DD_TRACE_MYSQL_ENABLED: boolean;
332
332
  DD_TRACE_MYSQL2_ENABLED: boolean;
333
333
  DD_TRACE_NATIVE_SPAN_EVENTS: boolean;
334
+ DD_TRACE_NATS_ENABLED: boolean;
334
335
  DD_TRACE_NET_ENABLED: boolean;
335
336
  DD_TRACE_NEXT_ENABLED: boolean;
336
337
  DD_TRACE_NODE_CHILD_PROCESS_ENABLED: boolean;
@@ -428,6 +429,9 @@ export interface GeneratedConfig {
428
429
  flaggingProvider: {
429
430
  enabled: boolean;
430
431
  initializationTimeoutMs: number;
432
+ spanEnrichment: {
433
+ enabled: boolean;
434
+ };
431
435
  };
432
436
  };
433
437
  flakyTestRetriesCount: number;
@@ -640,7 +640,9 @@
640
640
  "configurationNames": [
641
641
  "dbmPropagationMode"
642
642
  ],
643
- "default": "disabled"
643
+ "default": "disabled",
644
+ "allowed": "disabled|service|full|dynamic_service",
645
+ "transform": "toLowerCase"
644
646
  }
645
647
  ],
646
648
  "DD_DOGSTATSD_HOST": [
@@ -773,6 +775,16 @@
773
775
  "default": "false"
774
776
  }
775
777
  ],
778
+ "DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED": [
779
+ {
780
+ "implementation": "A",
781
+ "type": "boolean",
782
+ "configurationNames": [
783
+ "experimental.flaggingProvider.spanEnrichment.enabled"
784
+ ],
785
+ "default": "false"
786
+ }
787
+ ],
776
788
  "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED": [
777
789
  {
778
790
  "implementation": "B",
@@ -1773,13 +1785,6 @@
1773
1785
  "internalPropertyName": "isTestManagementEnabled"
1774
1786
  }
1775
1787
  ],
1776
- "DD_TEST_TIA_KEEP_COV_CONFIG": [
1777
- {
1778
- "implementation": "A",
1779
- "type": "boolean",
1780
- "default": "false"
1781
- }
1782
- ],
1783
1788
  "DD_TEST_SESSION_NAME": [
1784
1789
  {
1785
1790
  "implementation": "A",
@@ -2176,6 +2181,13 @@
2176
2181
  "default": "true"
2177
2182
  }
2178
2183
  ],
2184
+ "DD_TRACE_AZURE_COSMOS_ENABLED": [
2185
+ {
2186
+ "implementation": "A",
2187
+ "type": "boolean",
2188
+ "default": "true"
2189
+ }
2190
+ ],
2179
2191
  "DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED": [
2180
2192
  {
2181
2193
  "implementation": "B",
@@ -3184,6 +3196,13 @@
3184
3196
  "default": "false"
3185
3197
  }
3186
3198
  ],
3199
+ "DD_TRACE_NATS_ENABLED": [
3200
+ {
3201
+ "implementation": "A",
3202
+ "type": "boolean",
3203
+ "default": "false"
3204
+ }
3205
+ ],
3187
3206
  "DD_TRACE_NET_ENABLED": [
3188
3207
  {
3189
3208
  "implementation": "A",
@@ -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