dd-trace 6.12.0 → 6.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -66,15 +66,6 @@ const tagValueExpr = /^[\x20-\x2B\x2D-\x7E]*$/ // ASCII minus commas
66
66
  // https://github.com/nodejs/node/blob/main/lib/_http_common.js
67
67
  const invalidHeaderValueCharExpr = /[^\t\x20-\x7E\x80-\xFF]/
68
68
  const traceparentExpr = /^([a-f0-9]{2})-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})(-.*)?$/i
69
- // Dispatch table for `_extractSpanContext`. `'b3'` resolves to the matching
70
- // single/multi extractor per instance — see `#b3MethodName` — so it is not in
71
- // this table. `'baggage'` is consumed by `_extractBaggageItems`, not the loop.
72
- const EXTRACT_STYLE_METHODS = new Map([
73
- ['datadog', '_extractDatadogContext'],
74
- ['tracecontext', '_extractTraceparentContext'],
75
- ['b3 single header', '_extractB3SingleContext'],
76
- ['b3multi', '_extractB3MultiContext'],
77
- ])
78
69
  // Origin value in tracestate replaces '~', ',' and ';' with '_"
79
70
  const tracestateOriginFilter = /[^\x20-\x2B\x2D-\x3A\x3C-\x7D]/g
80
71
  // Tag keys in tracestate replace ' ', ',' and '=' with '_'
@@ -86,35 +77,228 @@ const zeroTraceId = '0000000000000000'
86
77
  const hex16 = /^[0-9A-Fa-f]{16}$/
87
78
  const percentByte = /%([0-9A-Fa-f]{2})/g
88
79
 
80
+ /**
81
+ * @typedef {object} B3Context
82
+ * @property {string} [flags]
83
+ * @property {string} [sampled]
84
+ * @property {string} [spanId]
85
+ * @property {string} [traceId]
86
+ */
87
+
88
+ /**
89
+ * @param {Array<string | undefined>} traceTagReplacements
90
+ * @param {string} key
91
+ * @returns {boolean}
92
+ */
93
+ function hasTraceTagReplacement (traceTagReplacements, key) {
94
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
95
+ if (traceTagReplacements[index] === key) return true
96
+ }
97
+ return false
98
+ }
99
+
100
+ /**
101
+ * @param {string | undefined} traceId
102
+ * @param {string | undefined} spanId
103
+ * @param {number} radix
104
+ * @returns {DatadogSpanContext | undefined}
105
+ */
106
+ function extractGenericContext (traceId, spanId, radix) {
107
+ if (!traceId || invalidSegment.test(traceId)) return
108
+ if (!spanId) return
109
+
110
+ return new DatadogSpanContext({
111
+ traceId: id(traceId, radix),
112
+ spanId: id(spanId, radix),
113
+ isRemote: true,
114
+ })
115
+ }
116
+
117
+ /**
118
+ * @param {string} traceId
119
+ * @param {DatadogSpanContext} spanContext
120
+ * @returns {void}
121
+ */
122
+ function extract128BitTraceId (traceId, spanContext) {
123
+ const buffer = spanContext._traceId.toBuffer()
124
+
125
+ if (buffer.length !== 16) return
126
+
127
+ const tid = traceId.slice(0, 16)
128
+
129
+ if (tid === zeroTraceId) return
130
+
131
+ spanContext._trace.tags['_dd.p.tid'] = tid
132
+ }
133
+
134
+ /**
135
+ * @param {string | undefined} sampled
136
+ * @param {boolean} debug
137
+ * @returns {import('../../priority_sampler').SamplingPriority | undefined}
138
+ */
139
+ function getB3Priority (sampled, debug) {
140
+ if (debug) {
141
+ return USER_KEEP
142
+ } else if (sampled === '1') {
143
+ return AUTO_KEEP
144
+ } else if (sampled === '0') {
145
+ return AUTO_REJECT
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @param {B3Context} b3
151
+ * @returns {DatadogSpanContext | undefined}
152
+ */
153
+ function extractB3Context (b3) {
154
+ const priority = getB3Priority(b3.sampled, b3.flags === '1')
155
+ const spanContext = extractGenericContext(b3.traceId, b3.spanId, 16)
156
+
157
+ if (priority !== undefined) {
158
+ if (!spanContext) {
159
+ return new DatadogSpanContext({
160
+ traceId: id(),
161
+ spanId: null,
162
+ sampling: { priority },
163
+ isRemote: true,
164
+ })
165
+ }
166
+
167
+ spanContext._sampling.priority = priority
168
+ }
169
+
170
+ if (spanContext && b3.traceId) extract128BitTraceId(b3.traceId, spanContext)
171
+
172
+ return spanContext
173
+ }
174
+
175
+ /**
176
+ * @param {Record<string, unknown>} carrier
177
+ * @returns {B3Context | undefined}
178
+ */
179
+ function extractB3MultipleHeaders (carrier) {
180
+ // Parent ID is intentionally not a standalone signal for B3 extraction.
181
+ const traceId = readB3TraceId(carrier)
182
+ const sampled = readB3Sampled(carrier)
183
+ const flags = readB3Flags(carrier)
184
+
185
+ if (traceId === undefined && sampled === undefined && flags === undefined) return
186
+
187
+ let empty = true
188
+ const b3 = {}
189
+ const spanId = readB3SpanId(carrier)
190
+
191
+ if (traceId && spanId && b3TraceExpr.test(traceId) && b3SpanExpr.test(spanId)) {
192
+ b3.traceId = traceId
193
+ b3.spanId = spanId
194
+ empty = false
195
+ }
196
+
197
+ if (sampled) {
198
+ b3.sampled = sampled
199
+ empty = false
200
+ }
201
+
202
+ if (flags) {
203
+ b3.flags = flags
204
+ empty = false
205
+ }
206
+
207
+ return empty ? undefined : b3
208
+ }
209
+
210
+ /**
211
+ * @param {string} header
212
+ * @returns {B3Context}
213
+ */
214
+ function extractB3SingleHeader (header) {
215
+ const traceIdEnd = header.indexOf('-')
216
+
217
+ if (traceIdEnd === -1) {
218
+ if (header === 'd') {
219
+ return {
220
+ sampled: '1',
221
+ flags: '1',
222
+ }
223
+ }
224
+ return {
225
+ sampled: header,
226
+ }
227
+ }
228
+
229
+ const spanIdStart = traceIdEnd + 1
230
+ const spanIdEnd = spanIdStart + 16
231
+ const b3 = {
232
+ traceId: header.slice(0, traceIdEnd),
233
+ spanId: header.slice(spanIdStart, spanIdEnd),
234
+ }
235
+
236
+ if (header.length > spanIdEnd) {
237
+ const sampled = header[spanIdEnd + 1]
238
+ b3.sampled = sampled === '0' ? '0' : '1'
239
+
240
+ if (sampled === 'd') {
241
+ b3.flags = '1'
242
+ }
243
+ }
244
+
245
+ return b3
246
+ }
247
+
248
+ /**
249
+ * @param {Record<string, unknown>} carrier
250
+ * @returns {DatadogSpanContext | undefined}
251
+ */
252
+ function extractB3MultiContext (carrier) {
253
+ const b3 = extractB3MultipleHeaders(carrier)
254
+ if (b3 === undefined) return
255
+ return extractB3Context(b3)
256
+ }
257
+
258
+ /**
259
+ * @param {Record<string, unknown>} carrier
260
+ * @returns {DatadogSpanContext | undefined}
261
+ */
262
+ function extractB3SingleContext (carrier) {
263
+ // Resolve the value before running the regex on the common header-less path.
264
+ const header = readB3(carrier)
265
+ if (!header || !b3HeaderExpr.test(header)) return
266
+ return extractB3Context(extractB3SingleHeader(header))
267
+ }
268
+
89
269
  class TextMapPropagator {
90
- /** @type {Set<string> | undefined} Cached `Set` view of `_config.baggageTagKeys`. */
270
+ /** @type {Set<string> | undefined} Cached `Set` view of `#config.baggageTagKeys`. */
91
271
  #baggageTagKeysSet
92
272
 
93
273
  /** @type {string[] | undefined} Source array that `#baggageTagKeysSet` was built from. */
94
274
  #baggageTagKeysSetSource
95
275
 
96
- /** @type {'_extractB3SingleContext' | '_extractB3MultiContext'} */
97
- #b3MethodName
276
+ /** @type {import('../../config')} */
277
+ #config
98
278
 
279
+ /** @type {typeof extractB3SingleContext | typeof extractB3MultiContext} */
280
+ #extractB3Context
281
+
282
+ /** @param {import('../../config')} config */
99
283
  constructor (config) {
100
- this._config = config
284
+ this.#config = config
101
285
 
102
- // v6: `'b3'` is always single-header. v5: `OTEL_PROPAGATORS` callers
286
+ // v6+: `'b3'` is always single-header. v5: `OTEL_PROPAGATORS` callers
103
287
  // expect single, legacy `DD_TRACE_PROPAGATION_STYLE` callers expect multi.
104
- /* istanbul ignore else: v5 fallback, master ships 6.0.0-pre */
288
+ /* istanbul ignore else: v5 fallback */
105
289
  if (DD_MAJOR >= 6) {
106
- this.#b3MethodName = '_extractB3SingleContext'
290
+ this.#extractB3Context = extractB3SingleContext
107
291
  } else {
108
292
  const envName = getConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
109
293
  // eslint-disable-next-line eslint-rules/eslint-env-aliases
110
- this.#b3MethodName = envName === 'OTEL_PROPAGATORS'
111
- ? '_extractB3SingleContext'
112
- : '_extractB3MultiContext'
294
+ this.#extractB3Context = envName === 'OTEL_PROPAGATORS'
295
+ ? extractB3SingleContext
296
+ : extractB3MultiContext
113
297
  }
114
298
  }
115
299
 
116
300
  /**
117
- * Returns a `Set` view of `_config.baggageTagKeys` that is rebuilt only
301
+ * Returns a `Set` view of `#config.baggageTagKeys` that is rebuilt only
118
302
  * when the source array reference changes. Avoids an `O(n)` `Set` alloc
119
303
  * per baggage extract (which is per-request when baggage propagation is
120
304
  * enabled).
@@ -122,7 +306,7 @@ class TextMapPropagator {
122
306
  * @returns {Set<string>}
123
307
  */
124
308
  #getBaggageTagKeysSet () {
125
- const source = this._config.baggageTagKeys
309
+ const source = this.#config.baggageTagKeys
126
310
  if (this.#baggageTagKeysSetSource !== source) {
127
311
  this.#baggageTagKeysSet = new Set(source)
128
312
  this.#baggageTagKeysSetSource = source
@@ -137,23 +321,36 @@ class TextMapPropagator {
137
321
  */
138
322
  inject (spanContext, carrier) {
139
323
  if (carrier === null) return
140
- let injectedCarrier = this._injectBaggageItems(spanContext, carrier)
324
+ let injectedCarrier = this.#injectBaggageItems(spanContext, carrier)
141
325
  if (!spanContext) return injectedCarrier
142
326
 
143
- const injectTraceContext = this._config.apmTracingEnabled !== false ||
327
+ const injectTraceContext = this.#config.apmTracingEnabled !== false ||
144
328
  hasTraceSourcePropagationTag(spanContext._trace.tags)
329
+ let traceTagReplacements
330
+ let optionalTraceTagCount = 0
331
+ if (injectTraceContext && injectCh.hasSubscribers && (
332
+ this.#hasPropagationStyle('inject', 'datadog') || this.#hasPropagationStyle('inject', 'tracecontext')
333
+ )) {
334
+ const injection = { spanContext }
335
+ injectCh.publish(injection)
336
+ traceTagReplacements = injection.traceTagReplacements
337
+ optionalTraceTagCount = injection.optionalTraceTagCount ?? 0
338
+ }
145
339
  if (injectTraceContext) {
146
- injectedCarrier = this._injectDatadog(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
147
- injectedCarrier = this._injectB3MultipleHeaders(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
148
- injectedCarrier = this._injectB3SingleHeader(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
340
+ injectedCarrier = this.#injectDatadog(
341
+ spanContext, injectedCarrier ?? carrier, traceTagReplacements, optionalTraceTagCount
342
+ ) ?? injectedCarrier
343
+ injectedCarrier = this.#injectB3MultipleHeaders(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
344
+ injectedCarrier = this.#injectB3SingleHeader(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
149
345
  }
150
346
  injectedCarrier = this
151
- ._injectTraceparent(spanContext, injectedCarrier ?? carrier, injectTraceContext) ?? injectedCarrier
347
+ .#injectTraceparent(
348
+ spanContext, injectedCarrier ?? carrier, injectTraceContext, traceTagReplacements
349
+ ) ?? injectedCarrier
152
350
 
153
351
  if (injectedCarrier === undefined) return
154
352
 
155
353
  carrier = injectedCarrier
156
- if (injectCh.hasSubscribers) injectCh.publish({ spanContext, carrier })
157
354
 
158
355
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
159
356
  log.debug(() => `Inject into carrier: ${JSON.stringify(pickTextMap(carrier))}.`)
@@ -161,8 +358,12 @@ class TextMapPropagator {
161
358
  return carrier
162
359
  }
163
360
 
361
+ /**
362
+ * @param {Record<string, unknown>} carrier
363
+ * @returns {DatadogSpanContext | null}
364
+ */
164
365
  extract (carrier) {
165
- const spanContext = this._extractSpanContext(carrier)
366
+ const spanContext = this.#extractSpanContext(carrier)
166
367
  if (spanContext === undefined) return null
167
368
 
168
369
  if (extractCh.hasSubscribers) {
@@ -172,7 +373,7 @@ class TextMapPropagator {
172
373
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
173
374
  log.debug(() => {
174
375
  const keys = JSON.stringify(pickTextMap(carrier))
175
- const styles = this._config.tracePropagationStyle.extract.join(', ')
376
+ const styles = this.#config.tracePropagationStyle.extract.join(', ')
176
377
 
177
378
  return `Extract from carrier (${styles}): ${keys}.`
178
379
  })
@@ -183,36 +384,26 @@ class TextMapPropagator {
183
384
  /**
184
385
  * @param {DatadogSpanContext} spanContext
185
386
  * @param {Record<string, string>} [carrier]
387
+ * @param {Array<string | undefined>} [traceTagReplacements]
388
+ * @param {number} optionalTraceTagCount
186
389
  * @returns {Record<string, string> | undefined}
187
390
  */
188
- _injectDatadog (spanContext, carrier) {
189
- if (!this._hasPropagationStyle('inject', 'datadog')) return
391
+ #injectDatadog (spanContext, carrier, traceTagReplacements, optionalTraceTagCount) {
392
+ if (!this.#hasPropagationStyle('inject', 'datadog')) return
190
393
 
191
394
  carrier ??= {}
192
395
  writeDatadogTraceId(carrier, spanContext.toTraceId())
193
396
  writeDatadogParentId(carrier, spanContext.toSpanId())
194
397
 
195
- this._injectOrigin(spanContext, carrier)
196
- this._injectSamplingPriority(spanContext, carrier)
197
- this._injectTags(spanContext, carrier)
198
-
199
- return carrier
200
- }
201
-
202
- _injectOrigin (spanContext, carrier) {
203
398
  const origin = spanContext._trace.origin
399
+ if (origin) writeDatadogOrigin(carrier, origin)
204
400
 
205
- if (origin) {
206
- writeDatadogOrigin(carrier, origin)
207
- }
208
- }
209
-
210
- _injectSamplingPriority (spanContext, carrier) {
211
401
  const priority = spanContext._sampling.priority
402
+ if (Number.isInteger(priority)) writeDatadogSamplingPriority(carrier, priority.toString())
212
403
 
213
- if (Number.isInteger(priority)) {
214
- writeDatadogSamplingPriority(carrier, priority.toString())
215
- }
404
+ this.#injectTags(carrier, spanContext._trace.tags, traceTagReplacements, optionalTraceTagCount)
405
+
406
+ return carrier
216
407
  }
217
408
 
218
409
  /**
@@ -220,9 +411,9 @@ class TextMapPropagator {
220
411
  * @param {Record<string, string>} [carrier]
221
412
  * @returns {Record<string, string> | undefined}
222
413
  */
223
- _injectBaggageItems (spanContext, carrier) {
414
+ #injectBaggageItems (spanContext, carrier) {
224
415
  let injectedCarrier
225
- if (this._config.legacyBaggageEnabled) {
416
+ if (this.#config.legacyBaggageEnabled) {
226
417
  const baggageItems = spanContext?._baggageItems
227
418
  if (baggageItems) {
228
419
  for (const key of Object.keys(baggageItems)) {
@@ -241,7 +432,7 @@ class TextMapPropagator {
241
432
  }
242
433
  }
243
434
 
244
- if (this._hasPropagationStyle('inject', 'baggage')) {
435
+ if (this.#hasPropagationStyle('inject', 'baggage')) {
245
436
  let baggage = ''
246
437
  let itemCounter = 0
247
438
  let byteCounter = 0
@@ -259,13 +450,13 @@ class TextMapPropagator {
259
450
  byteCounter += item.length
260
451
 
261
452
  // Check for item count limit exceeded
262
- if (itemCounter > this._config.baggageMaxItems) {
453
+ if (itemCounter > this.#config.baggageMaxItems) {
263
454
  tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_item_count_exceeded']).inc()
264
455
  break
265
456
  }
266
457
 
267
458
  // Check for byte count limit exceeded
268
- if (byteCounter > this._config.baggageMaxBytes) {
459
+ if (byteCounter > this.#config.baggageMaxBytes) {
269
460
  tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_byte_count_exceeded']).inc()
270
461
  break
271
462
  }
@@ -284,30 +475,55 @@ class TextMapPropagator {
284
475
  return injectedCarrier
285
476
  }
286
477
 
287
- _injectTags (spanContext, carrier) {
288
- const trace = spanContext._trace
289
-
290
- if (this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
478
+ /**
479
+ * @param {Record<string, string>} carrier
480
+ * @param {Record<string, string>} traceTags
481
+ * @param {Array<string | undefined>} [traceTagReplacements]
482
+ * @param {number} optionalTraceTagCount
483
+ * @returns {void}
484
+ */
485
+ #injectTags (carrier, traceTags, traceTagReplacements, optionalTraceTagCount) {
486
+ if (this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
291
487
  log.debug('Trace tag propagation is disabled, skipping injection.')
292
488
  return
293
489
  }
294
490
 
295
- const tags = []
491
+ const maxLength = this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH
492
+ let header = ''
296
493
 
297
- for (const key of Object.keys(trace.tags)) {
298
- const value = trace.tags[key]
299
- if (!value || !key.startsWith('_dd.p.')) continue
300
- if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
494
+ for (const key of Object.keys(traceTags)) {
495
+ const value = traceTags[key]
496
+ if (!value || !key.startsWith('_dd.p.') ||
497
+ traceTagReplacements && hasTraceTagReplacement(traceTagReplacements, key)) {
498
+ continue
499
+ }
500
+ if (!tagKeyExpr.test(key) || !tagValueExpr.test(value)) {
301
501
  log.error('Trace tags from span are invalid, skipping injection.')
302
502
  return
303
503
  }
304
504
 
305
- tags.push(`${key}=${value}`)
505
+ if (header) header += ','
506
+ header += `${key}=${value}`
306
507
  }
307
508
 
308
- const header = tags.join(',')
509
+ if (traceTagReplacements) {
510
+ const requiredEntryCount = traceTagReplacements.length - optionalTraceTagCount * 2
511
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
512
+ const key = traceTagReplacements[index]
513
+ const value = traceTagReplacements[index + 1]
514
+ if (!value || !key.startsWith('_dd.p.')) continue
515
+ if (!tagKeyExpr.test(key) || !tagValueExpr.test(value)) {
516
+ log.error('Trace tags from span are invalid, skipping injection.')
517
+ return
518
+ }
309
519
 
310
- if (header.length > this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
520
+ const entry = `${header ? ',' : ''}${key}=${value}`
521
+ if (index >= requiredEntryCount && header.length + entry.length > maxLength) break
522
+ header += entry
523
+ }
524
+ }
525
+
526
+ if (header.length > maxLength) {
311
527
  log.error('Trace tags from span are too large, skipping injection.')
312
528
  } else if (header) {
313
529
  writeDatadogTags(carrier, header)
@@ -319,10 +535,10 @@ class TextMapPropagator {
319
535
  * @param {Record<string, string>} [carrier]
320
536
  * @returns {Record<string, string> | undefined}
321
537
  */
322
- _injectB3MultipleHeaders (spanContext, carrier) {
323
- // v5 also accepts the legacy `'b3'` spelling for multi; v6 routes `'b3'` to single-header.
324
- const hasB3multi = this._hasPropagationStyle('inject', 'b3multi') ||
325
- (DD_MAJOR < 6 && this._hasPropagationStyle('inject', 'b3'))
538
+ #injectB3MultipleHeaders (spanContext, carrier) {
539
+ // v5 also accepts the legacy `'b3'` spelling for multi; v6+ routes `'b3'` to single-header.
540
+ const hasB3multi = this.#hasPropagationStyle('inject', 'b3multi') ||
541
+ (DD_MAJOR < 6 && this.#hasPropagationStyle('inject', 'b3'))
326
542
  if (!hasB3multi) return
327
543
 
328
544
  carrier ??= {}
@@ -346,10 +562,10 @@ class TextMapPropagator {
346
562
  * @param {Record<string, string>} [carrier]
347
563
  * @returns {Record<string, string> | undefined}
348
564
  */
349
- _injectB3SingleHeader (spanContext, carrier) {
350
- // v6 keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
351
- const hasB3SingleHeader = this._hasPropagationStyle('inject', 'b3 single header') ||
352
- (DD_MAJOR >= 6 && this._hasPropagationStyle('inject', 'b3'))
565
+ #injectB3SingleHeader (spanContext, carrier) {
566
+ // v6+ keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
567
+ const hasB3SingleHeader = this.#hasPropagationStyle('inject', 'b3 single header') ||
568
+ (DD_MAJOR >= 6 && this.#hasPropagationStyle('inject', 'b3'))
353
569
  if (!hasB3SingleHeader) return
354
570
 
355
571
  carrier ??= {}
@@ -370,10 +586,11 @@ class TextMapPropagator {
370
586
  * @param {DatadogSpanContext} spanContext
371
587
  * @param {Record<string, string> | undefined} carrier
372
588
  * @param {boolean} injectTraceContext
589
+ * @param {Array<string | undefined>} [traceTagReplacements]
373
590
  * @returns {Record<string, string> | undefined}
374
591
  */
375
- _injectTraceparent (spanContext, carrier, injectTraceContext) {
376
- if (!this._hasPropagationStyle('inject', 'tracecontext')) return
592
+ #injectTraceparent (spanContext, carrier, injectTraceContext, traceTagReplacements) {
593
+ if (!this.#hasPropagationStyle('inject', 'tracecontext')) return
377
594
 
378
595
  if (!injectTraceContext) {
379
596
  const tracestate = TraceState.fromString(spanContext._tracestate?.toString())
@@ -388,9 +605,12 @@ class TextMapPropagator {
388
605
  carrier ??= {}
389
606
  const {
390
607
  _sampling: { priority, mechanism },
391
- _tracestate: ts = new TraceState(),
392
- _trace: { origin, tags: traceTags },
608
+ _tracestate,
609
+ _trace: { origin },
393
610
  } = spanContext
611
+ const ts = traceTagReplacements
612
+ ? TraceState.fromString(_tracestate?.toString())
613
+ : _tracestate ?? new TraceState()
394
614
 
395
615
  writeTraceparent(carrier, spanContext.toTraceparent())
396
616
 
@@ -416,8 +636,9 @@ class TextMapPropagator {
416
636
  state.set('o', originValue)
417
637
  }
418
638
 
419
- for (const key of Object.keys(traceTags)) {
420
- const tagValueRaw = traceTags[key]
639
+ for (const key of Object.keys(spanContext._trace.tags)) {
640
+ if (traceTagReplacements && hasTraceTagReplacement(traceTagReplacements, key)) continue
641
+ const tagValueRaw = spanContext._trace.tags[key]
421
642
  if (!tagValueRaw || !key.startsWith('_dd.p.')) continue
422
643
 
423
644
  const tagKey = 't.' + key.slice(6)
@@ -430,60 +651,122 @@ class TextMapPropagator {
430
651
 
431
652
  state.set(tagKey, tagValue)
432
653
  }
433
- })
434
654
 
435
- writeTracestate(carrier, ts.toString())
655
+ if (traceTagReplacements) {
656
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
657
+ const key = traceTagReplacements[index]
658
+ if (!key.startsWith('_dd.p.')) continue
436
659
 
437
- return carrier
438
- }
660
+ const tagKey = 't.' + key.slice(6)
661
+ .replaceAll(tracestateTagKeyFilter, '_')
662
+ const tagValueRaw = traceTagReplacements[index + 1]
663
+ if (!tagValueRaw) {
664
+ state.delete(tagKey)
665
+ continue
666
+ }
439
667
 
440
- _hasPropagationStyle (mode, name) {
441
- return this._config.tracePropagationStyle[mode].includes(name)
442
- }
668
+ const tagValue = tagValueRaw
669
+ .toString()
670
+ .replaceAll(tracestateTagValueFilter, '_')
671
+ .replaceAll('=', '~')
443
672
 
444
- _hasTraceIdConflict (w3cSpanContext, firstSpanContext) {
445
- return w3cSpanContext !== undefined &&
446
- firstSpanContext.toTraceId(true) === w3cSpanContext.toTraceId(true) &&
447
- firstSpanContext.toSpanId() !== w3cSpanContext.toSpanId()
448
- }
673
+ state.set(tagKey, tagValue)
674
+ }
675
+ }
676
+ })
677
+
678
+ writeTracestate(carrier, ts.toString())
449
679
 
450
- _hasParentIdInTags (spanContext) {
451
- return tags.DD_PARENT_ID in spanContext._trace.tags
680
+ return carrier
452
681
  }
453
682
 
454
- _updateParentIdFromDdHeaders (carrier, firstSpanContext) {
455
- const ddCtx = this._extractDatadogContext(carrier)
456
- if (ddCtx !== undefined) {
457
- firstSpanContext._trace.tags[tags.DD_PARENT_ID] = ddCtx._spanId.toString().padStart(16, '0')
458
- }
683
+ /**
684
+ * @param {'inject' | 'extract'} mode
685
+ * @param {string} name
686
+ * @returns {boolean}
687
+ */
688
+ #hasPropagationStyle (mode, name) {
689
+ return this.#config.tracePropagationStyle[mode].includes(name)
459
690
  }
460
691
 
461
- _resolveTraceContextConflicts (w3cSpanContext, firstSpanContext, carrier) {
462
- if (!this._hasTraceIdConflict(w3cSpanContext, firstSpanContext)) {
692
+ /**
693
+ * @param {DatadogSpanContext | undefined} w3cSpanContext
694
+ * @param {DatadogSpanContext} firstSpanContext
695
+ * @param {Record<string, unknown>} carrier
696
+ * @param {DatadogSpanContext | undefined} datadogContext
697
+ * @returns {DatadogSpanContext}
698
+ */
699
+ #resolveTraceContextConflicts (w3cSpanContext, firstSpanContext, carrier, datadogContext) {
700
+ if (w3cSpanContext === undefined ||
701
+ firstSpanContext.toTraceId(true) !== w3cSpanContext.toTraceId(true) ||
702
+ firstSpanContext.toSpanId() === w3cSpanContext.toSpanId()) {
463
703
  return firstSpanContext
464
704
  }
465
- if (this._hasParentIdInTags(w3cSpanContext)) {
705
+ if (tags.DD_PARENT_ID in w3cSpanContext._trace.tags) {
466
706
  // tracecontext headers contain a p value, ensure this value is sent to backend
467
707
  firstSpanContext._trace.tags[tags.DD_PARENT_ID] = w3cSpanContext._trace.tags[tags.DD_PARENT_ID]
468
708
  } else {
469
709
  // if p value is not present in tracestate, use the parent id from the datadog headers
470
- this._updateParentIdFromDdHeaders(carrier, firstSpanContext)
710
+ datadogContext ||= extractGenericContext(readDatadogTraceId(carrier), readDatadogParentId(carrier), 10)
711
+ if (datadogContext) {
712
+ firstSpanContext._trace.tags[tags.DD_PARENT_ID] = datadogContext._spanId.toString().padStart(16, '0')
713
+ }
471
714
  }
472
715
  // the span_id in tracecontext takes precedence over the first extracted propagation style
473
716
  firstSpanContext._spanId = w3cSpanContext._spanId
474
717
  return firstSpanContext
475
718
  }
476
719
 
477
- _extractSpanContext (carrier) {
720
+ /**
721
+ * @param {Record<string, unknown>} carrier
722
+ * @returns {DatadogSpanContext | undefined}
723
+ */
724
+ #extractSpanContext (carrier) {
478
725
  let context
726
+ let datadogContext
479
727
  let style = ''
480
- for (const extractor of this._config.tracePropagationStyle.extract) {
481
- const method = extractor === 'b3' ? this.#b3MethodName : EXTRACT_STYLE_METHODS.get(extractor)
482
- if (method === undefined) {
483
- if (extractor !== 'baggage') log.warn('Unknown propagation style:', extractor)
484
- continue
728
+ let extractBaggage = false
729
+ let traceContext
730
+ let traceContextExtracted = false
731
+ for (const extractor of this.#config.tracePropagationStyle.extract) {
732
+ let extractedContext
733
+ switch (extractor) {
734
+ case 'datadog':
735
+ datadogContext = this.#extractDatadogContext(carrier)
736
+ extractedContext = datadogContext
737
+ if (extractedContext !== undefined && !this.#config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
738
+ if (!traceContextExtracted) {
739
+ traceContext = this.#extractTraceparentContext(carrier)
740
+ traceContextExtracted = true
741
+ }
742
+ this.#addTraceContextState(extractedContext, traceContext)
743
+ }
744
+ break
745
+ case 'tracecontext':
746
+ if (!traceContextExtracted) {
747
+ traceContext = this.#extractTraceparentContext(carrier)
748
+ traceContextExtracted = true
749
+ }
750
+ extractedContext = traceContext
751
+ break
752
+ case 'b3':
753
+ extractedContext = this.#extractB3Context(carrier)
754
+ break
755
+ case 'b3 single header':
756
+ extractedContext = extractB3SingleContext(carrier)
757
+ break
758
+ case 'b3multi':
759
+ extractedContext = extractB3MultiContext(carrier)
760
+ break
761
+ case 'baggage':
762
+ extractBaggage = true
763
+ continue
764
+ case 'none':
765
+ continue
766
+ default:
767
+ log.warn('Unknown propagation style:', extractor)
768
+ continue
485
769
  }
486
- const extractedContext = this[method](carrier)
487
770
  if (extractedContext === undefined) {
488
771
  continue
489
772
  }
@@ -491,14 +774,14 @@ class TextMapPropagator {
491
774
  if (context === undefined) {
492
775
  context = extractedContext
493
776
  style = extractor
494
- if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
777
+ if (this.#config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
495
778
  break
496
779
  }
497
780
  } else {
498
781
  // If extractor is tracecontext, add tracecontext specific information to the context
499
782
  if (extractor === 'tracecontext') {
500
- context = this._resolveTraceContextConflicts(
501
- this._extractTraceparentContext(carrier), context, carrier)
783
+ context = this.#resolveTraceContextConflicts(
784
+ extractedContext, context, carrier, datadogContext)
502
785
  }
503
786
  if (extractedContext._traceId && extractedContext._spanId &&
504
787
  extractedContext.toTraceId(true) !== context.toTraceId(true)) {
@@ -511,10 +794,14 @@ class TextMapPropagator {
511
794
  }
512
795
  }
513
796
 
514
- if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
797
+ if (context && (style === 'datadog' || style === 'tracecontext')) {
798
+ this.#extractLegacyBaggageItems(carrier, context)
799
+ }
800
+
801
+ if (this.#config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
515
802
  if (context !== undefined) context._links = []
516
803
  } else {
517
- if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart' && context) {
804
+ if (this.#config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart' && context) {
518
805
  context._links = []
519
806
  context._links.push({
520
807
  context,
@@ -524,78 +811,47 @@ class TextMapPropagator {
524
811
  },
525
812
  })
526
813
  }
527
- this._extractBaggageItems(carrier, context)
814
+ if (!extractBaggage && this.#config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
815
+ extractBaggage = this.#hasPropagationStyle('extract', 'baggage')
816
+ }
817
+ this.#extractBaggageItems(carrier, context, extractBaggage)
528
818
  }
529
819
 
530
- return context || this._extractSqsdContext(carrier)
820
+ return context || this.#extractSqsdContext(carrier)
531
821
  }
532
822
 
533
- _extractDatadogContext (carrier) {
823
+ /**
824
+ * @param {Record<string, unknown>} carrier
825
+ * @returns {DatadogSpanContext | undefined}
826
+ */
827
+ #extractDatadogContext (carrier) {
534
828
  if (!carrier) return
535
- const spanContext = this._extractGenericContext(
536
- readDatadogTraceId(carrier),
537
- readDatadogParentId(carrier),
538
- 10
539
- )
829
+ const traceId = readDatadogTraceId(carrier)
830
+ if (!traceId) return
831
+ const spanContext = extractGenericContext(traceId, readDatadogParentId(carrier), 10)
540
832
 
541
833
  if (!spanContext) return spanContext
542
834
 
543
- this._extractOrigin(carrier, spanContext)
544
- this._extractLegacyBaggageItems(carrier, spanContext)
545
- this._extractSamplingPriority(carrier, spanContext)
546
- const traceTags = this._extractTags(carrier)
547
- if (traceTags) spanContext._trace.tags = traceTags
548
-
549
- if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) return spanContext
550
-
551
- const tc = this._extractTraceparentContext(carrier)
552
-
553
- if (tc && spanContext._traceId.equals(tc._traceId)) {
554
- spanContext._traceparent = tc._traceparent
555
- spanContext._tracestate = tc._tracestate
556
- }
557
-
558
- return spanContext
559
- }
560
-
561
- _extractB3MultiContext (carrier) {
562
- const b3 = this._extractB3MultipleHeaders(carrier)
563
- if (b3 === undefined) return
564
- return this._extractB3Context(b3)
565
- }
566
-
567
- _extractB3SingleContext (carrier) {
568
- // Check the resolved value first so the regex does not run on header-less requests.
569
- const header = readB3(carrier)
570
- if (!header || !b3HeaderExpr.test(header)) return
571
- return this._extractB3Context(this._extractB3SingleHeader(header))
572
- }
573
-
574
- _extractB3Context (b3) {
575
- const debug = b3.flags === '1'
576
- const priority = this._getPriority(b3.sampled, debug)
577
- const spanContext = this._extractGenericContext(b3.traceId, b3.spanId, 16)
578
-
579
- if (priority !== undefined) {
580
- if (!spanContext) {
581
- // B3 can force a sampling decision without providing IDs
582
- return new DatadogSpanContext({
583
- traceId: id(),
584
- spanId: null,
585
- sampling: { priority },
586
- isRemote: true,
587
- })
588
- }
835
+ const origin = readDatadogOrigin(carrier)
836
+ if (typeof origin === 'string') spanContext._trace.origin = origin
589
837
 
590
- spanContext._sampling.priority = priority
838
+ const header = readDatadogSamplingPriority(carrier)
839
+ if (header !== undefined) {
840
+ const priority = Number.parseInt(header, 10)
841
+ if (Number.isInteger(priority)) spanContext._sampling.priority = priority
591
842
  }
592
843
 
593
- this._extract128BitTraceId(b3.traceId, spanContext)
844
+ const traceTags = this.#extractTags(carrier)
845
+ if (traceTags) spanContext._trace.tags = traceTags
594
846
 
595
847
  return spanContext
596
848
  }
597
849
 
598
- _extractSqsdContext (carrier) {
850
+ /**
851
+ * @param {Record<string, unknown>} carrier
852
+ * @returns {DatadogSpanContext | undefined}
853
+ */
854
+ #extractSqsdContext (carrier) {
599
855
  const headerValue = readSqsd(carrier)
600
856
  if (!headerValue) return
601
857
  let parsed
@@ -604,10 +860,32 @@ class TextMapPropagator {
604
860
  } catch {
605
861
  return
606
862
  }
607
- return this._extractDatadogContext(parsed)
863
+ const spanContext = this.#extractDatadogContext(parsed)
864
+ if (!spanContext) return
865
+
866
+ this.#extractLegacyBaggageItems(parsed, spanContext)
867
+ if (this.#config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) return spanContext
868
+
869
+ this.#addTraceContextState(spanContext, this.#extractTraceparentContext(parsed))
870
+ return spanContext
608
871
  }
609
872
 
610
- _extractTraceparentContext (carrier) {
873
+ /**
874
+ * @param {DatadogSpanContext} datadogContext
875
+ * @param {DatadogSpanContext | undefined} traceContext
876
+ */
877
+ #addTraceContextState (datadogContext, traceContext) {
878
+ if (traceContext && datadogContext._traceId.equals(traceContext._traceId)) {
879
+ datadogContext._traceparent = traceContext._traceparent
880
+ datadogContext._tracestate = traceContext._tracestate
881
+ }
882
+ }
883
+
884
+ /**
885
+ * @param {Record<string, unknown>} carrier
886
+ * @returns {DatadogSpanContext | undefined}
887
+ */
888
+ #extractTraceparentContext (carrier) {
611
889
  const headerValue = readTraceparent(carrier)
612
890
  if (!headerValue) return
613
891
  const matches = headerValue.trim().match(traceparentExpr)
@@ -634,7 +912,7 @@ class TextMapPropagator {
634
912
  tracestate,
635
913
  })
636
914
 
637
- this._extract128BitTraceId(traceId, spanContext)
915
+ extract128BitTraceId(traceId, spanContext)
638
916
 
639
917
  tracestate.forVendor('dd', state => {
640
918
  for (const [key, value] of state.entries()) {
@@ -683,147 +961,78 @@ class TextMapPropagator {
683
961
  }
684
962
  })
685
963
 
686
- this._extractLegacyBaggageItems(carrier, spanContext)
687
964
  return spanContext
688
965
  }
689
966
  }
690
967
 
691
968
  /**
692
- * @param {string | undefined} traceId
693
- * @param {string | undefined} spanId
694
- * @param {number} radix
695
- * @returns {DatadogSpanContext | undefined}
969
+ * @param {Record<string, unknown>} carrier
970
+ * @param {DatadogSpanContext} spanContext
971
+ * @returns {void}
696
972
  */
697
- _extractGenericContext (traceId, spanId, radix) {
698
- if (!traceId || invalidSegment.test(traceId)) return
699
- if (!spanId) return
700
-
701
- return new DatadogSpanContext({
702
- traceId: id(traceId, radix),
703
- spanId: id(spanId, radix),
704
- isRemote: true,
705
- })
706
- }
707
-
708
- _extractB3MultipleHeaders (carrier) {
709
- // The parent-id field is intentionally absent: this method never consults it,
710
- // so a parent-id-only carrier should bail with the rest.
711
- const traceId = readB3TraceId(carrier)
712
- const sampled = readB3Sampled(carrier)
713
- const flags = readB3Flags(carrier)
714
-
715
- if (traceId === undefined && sampled === undefined && flags === undefined) {
716
- return
717
- }
718
-
719
- let empty = true
720
- const b3 = {}
721
- const spanId = readB3SpanId(carrier)
722
-
723
- if (traceId && spanId && b3TraceExpr.test(traceId) && b3SpanExpr.test(spanId)) {
724
- b3.traceId = traceId
725
- b3.spanId = spanId
726
- empty = false
727
- }
728
-
729
- if (sampled) {
730
- b3.sampled = sampled
731
- empty = false
732
- }
733
-
734
- if (flags) {
735
- b3.flags = flags
736
- empty = false
737
- }
738
-
739
- return empty ? undefined : b3
740
- }
741
-
742
- /** @param {string} header */
743
- _extractB3SingleHeader (header) {
744
- const parts = header.split('-')
745
-
746
- if (parts[0] === 'd') {
747
- return {
748
- sampled: '1',
749
- flags: '1',
750
- }
751
- } else if (parts.length === 1) {
752
- return {
753
- sampled: parts[0],
754
- }
755
- }
756
- const b3 = {
757
- traceId: parts[0],
758
- spanId: parts[1],
759
- }
760
-
761
- if (parts[2]) {
762
- b3.sampled = parts[2] === '0' ? '0' : '1'
763
-
764
- if (parts[2] === 'd') {
765
- b3.flags = '1'
766
- }
767
- }
768
-
769
- return b3
770
- }
771
-
772
- _extractOrigin (carrier, spanContext) {
773
- const origin = readDatadogOrigin(carrier)
774
-
775
- if (typeof origin === 'string') {
776
- spanContext._trace.origin = origin
777
- }
778
- }
779
-
780
- _extractLegacyBaggageItems (carrier, spanContext) {
781
- if (!this._config.legacyBaggageEnabled) return
973
+ #extractLegacyBaggageItems (carrier, spanContext) {
974
+ if (!this.#config.legacyBaggageEnabled) return
782
975
  readLegacyBaggage(carrier, spanContext._baggageItems)
783
976
  }
784
977
 
785
- _extractBaggageItems (carrier, spanContext) {
978
+ /**
979
+ * @param {Record<string, unknown> | undefined} carrier
980
+ * @param {DatadogSpanContext | undefined} spanContext
981
+ * @param {boolean} extractBaggage
982
+ * @returns {void}
983
+ */
984
+ #extractBaggageItems (carrier, spanContext, extractBaggage) {
786
985
  removeAllBaggageItems()
787
- if (!carrier || !this._hasPropagationStyle('extract', 'baggage')) return
986
+ if (!carrier || !extractBaggage) return
788
987
  const header = readBaggage(carrier)
789
988
  if (!header) return
790
989
 
791
- const baggages = header.split(',')
792
990
  const baggageTagKeys = this.#getBaggageTagKeysSet()
793
991
  const tagAllKeys = baggageTagKeys.has('*')
794
992
  /** @type {Record<string, string> | undefined} */
795
993
  let items
796
994
  let itemCount = 0
797
995
  let byteCount = 0
996
+ let start = 0
798
997
 
799
- for (const keyValue of baggages) {
800
- if (itemCount >= this._config.baggageMaxItems) {
998
+ while (start <= header.length) {
999
+ if (itemCount >= this.#config.baggageMaxItems) {
801
1000
  tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_item_count_exceeded']).inc()
802
1001
  break
803
1002
  }
1003
+
1004
+ const memberStart = start
1005
+ const commaIndex = header.indexOf(',', memberStart)
1006
+ const end = commaIndex === -1 ? header.length : commaIndex
1007
+
804
1008
  // Charge the comma slot before the empty-entry skip so a `,,,,,foo=bar` can't iterate for free.
805
- byteCount += keyValue.length + 1
806
- if (byteCount > this._config.baggageMaxBytes) {
1009
+ byteCount += end - memberStart + 1
1010
+ if (byteCount > this.#config.baggageMaxBytes) {
807
1011
  tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_byte_count_exceeded']).inc()
808
1012
  break
809
1013
  }
810
- if (!keyValue) continue
1014
+ if (memberStart === end) {
1015
+ start = end + 1
1016
+ continue
1017
+ }
811
1018
 
812
1019
  // Per W3C baggage, list-members can contain optional properties after `;`.
813
1020
  // Example: key=value;prop=1;prop2
814
1021
  // https://www.w3.org/TR/baggage/#header-content
815
- const semicolonIdx = keyValue.indexOf(';')
816
- const member = (semicolonIdx === -1 ? keyValue : keyValue.slice(0, semicolonIdx)).trim()
817
- if (!member) continue
818
-
819
- const eqIdx = member.indexOf('=')
820
- if (eqIdx === -1) {
1022
+ let memberEnd = header.indexOf(';', memberStart)
1023
+ if (memberEnd === -1 || memberEnd > end) memberEnd = end
1024
+ const equalsIndex = header.indexOf('=', memberStart)
1025
+ start = end + 1
1026
+
1027
+ if (equalsIndex === -1 || equalsIndex >= memberEnd) {
1028
+ const member = header.slice(memberStart, memberEnd).trim()
1029
+ if (!member) continue
821
1030
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
822
1031
  return
823
1032
  }
824
1033
 
825
- const key = member.slice(0, eqIdx).trim()
826
- let value = member.slice(eqIdx + 1).trim()
1034
+ const key = header.slice(memberStart, equalsIndex).trim()
1035
+ let value = header.slice(equalsIndex + 1, memberEnd).trim()
827
1036
 
828
1037
  if (!baggageTokenExpr.test(key) || !value) {
829
1038
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
@@ -856,27 +1065,17 @@ class TextMapPropagator {
856
1065
  }
857
1066
  }
858
1067
 
859
- _extractSamplingPriority (carrier, spanContext) {
860
- const header = readDatadogSamplingPriority(carrier)
861
- if (header === undefined) return
862
- const priority = Number.parseInt(header, 10)
863
-
864
- if (Number.isInteger(priority)) {
865
- spanContext._sampling.priority = priority
866
- }
867
- }
868
-
869
1068
  /**
870
1069
  * @param {Record<string, unknown>} carrier
871
1070
  * @returns {Record<string, string> | undefined}
872
1071
  */
873
- _extractTags (carrier) {
1072
+ #extractTags (carrier) {
874
1073
  const header = readDatadogTags(carrier)
875
1074
  if (!header) return
876
1075
 
877
- if (this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
1076
+ if (this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
878
1077
  log.debug('Trace tag propagation is disabled, skipping extraction.')
879
- } else if (header.length > this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
1078
+ } else if (header.length > this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
880
1079
  log.error('Trace tags from carrier are too large, skipping extraction.')
881
1080
  } else {
882
1081
  const tags = {}
@@ -890,7 +1089,7 @@ class TextMapPropagator {
890
1089
  const key = header.slice(start, hasSeparator ? separator : end)
891
1090
  const value = hasSeparator ? header.slice(separator + 1, end) : ''
892
1091
 
893
- if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
1092
+ if (!tagKeyExpr.test(key) || !tagValueExpr.test(value)) {
894
1093
  log.error('Trace tags from carrier are invalid, skipping extraction.')
895
1094
  return
896
1095
  }
@@ -907,62 +1106,6 @@ class TextMapPropagator {
907
1106
  return tags
908
1107
  }
909
1108
  }
910
-
911
- _extract128BitTraceId (traceId, spanContext) {
912
- if (!spanContext) return
913
-
914
- const buffer = spanContext._traceId.toBuffer()
915
-
916
- if (buffer.length !== 16) return
917
-
918
- const tid = traceId.slice(0, 16)
919
-
920
- if (tid === zeroTraceId) return
921
-
922
- spanContext._trace.tags['_dd.p.tid'] = tid
923
- }
924
-
925
- _validateTagKey (key) {
926
- return tagKeyExpr.test(key)
927
- }
928
-
929
- _validateTagValue (value) {
930
- return tagValueExpr.test(value)
931
- }
932
-
933
- _getPriority (sampled, debug) {
934
- if (debug) {
935
- return USER_KEEP
936
- } else if (sampled === '1') {
937
- return AUTO_KEEP
938
- } else if (sampled === '0') {
939
- return AUTO_REJECT
940
- }
941
- }
942
-
943
- /**
944
- * @param {number} traceparentSampled
945
- * @param {number|undefined} tracestateSamplingPriority
946
- * @param {string|null} origin
947
- * @returns {import('../../priority_sampler').SamplingPriority}
948
- */
949
- static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin) {
950
- const fromRumWithoutPriority = !tracestateSamplingPriority && origin === 'rum'
951
-
952
- let samplingPriority =
953
- /** @type {import('../../priority_sampler').SamplingPriority} */ (tracestateSamplingPriority ?? AUTO_KEEP)
954
- if (!fromRumWithoutPriority) {
955
- if (traceparentSampled === 0 &&
956
- (!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) {
957
- samplingPriority = AUTO_REJECT
958
- } else if (traceparentSampled === 1 &&
959
- (!tracestateSamplingPriority || tracestateSamplingPriority < 0)) {
960
- samplingPriority = AUTO_KEEP
961
- }
962
- }
963
-
964
- return samplingPriority
965
- }
966
1109
  }
967
1110
 
968
1111
  module.exports = TextMapPropagator