dd-trace 6.20.1 → 6.22.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 (225) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
@@ -24,7 +24,9 @@ function setAgentStrategy (config, setWritersAgentlessValue) {
24
24
  }
25
25
 
26
26
  const endpoints = agentInfo.endpoints
27
- const hasEndpoint = Array.isArray(endpoints) && endpoints.includes(EVP_PROXY_AGENT_BASE_PATH)
27
+ // The Lambda extension lists full proxy paths (e.g. /evp_proxy/v2/api/v2/llmobs), not the bare prefix.
28
+ const hasEndpoint = Array.isArray(endpoints) &&
29
+ endpoints.some(endpoint => typeof endpoint === 'string' && endpoint.startsWith(EVP_PROXY_AGENT_BASE_PATH))
28
30
  setWritersAgentlessValue(!hasEndpoint)
29
31
  })
30
32
  }
@@ -17,11 +17,7 @@ class Log {
17
17
  get formatted () {
18
18
  const { message, args } = this
19
19
 
20
- let formatted = message
21
- if (message && args && args.length) {
22
- formatted = format(message, ...args)
23
- }
24
- return formatted
20
+ return message && args && args.length ? format(message, ...args) : message
25
21
  }
26
22
 
27
23
  static parse (...args) {
@@ -21,6 +21,7 @@ const RETRY_JITTER = 0.2
21
21
  * @property {number} pollIntervalMs
22
22
  * @property {number} requestTimeoutMs
23
23
  * @property {string | undefined} apiKey
24
+ * @property {string | undefined} env
24
25
  */
25
26
 
26
27
  /**
@@ -106,9 +107,23 @@ class AgentlessConfigurationSource {
106
107
  */
107
108
  async #pollOnce (abortController) {
108
109
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
110
+ log.debug('Feature Flags: agentless configuration poll attempt %d/%d', attempt, MAX_ATTEMPTS)
109
111
  const response = await this.#request(abortController.signal)
110
112
  if (this.#abortController !== abortController) return
111
113
 
114
+ if (response.statusCode === 304) {
115
+ // 304 means the cached configuration is still valid, not a failure.
116
+ log.debug('Feature Flags: agentless configuration poll attempt %d/%d returned HTTP 304 (unchanged)',
117
+ attempt, MAX_ATTEMPTS)
118
+ } else if (response.statusCode === undefined) {
119
+ log.debug('Feature Flags: agentless configuration poll attempt %d/%d failed: %s',
120
+ attempt, MAX_ATTEMPTS, errorMessage(response.error))
121
+ } else {
122
+ // Avoids errorMessage(response.error), which embeds the full request URL.
123
+ log.debug('Feature Flags: agentless configuration poll attempt %d/%d returned HTTP %d',
124
+ attempt, MAX_ATTEMPTS, response.statusCode)
125
+ }
126
+
112
127
  const retryable = response.statusCode === undefined || isRetryableStatus(response.statusCode)
113
128
  if (!retryable) {
114
129
  this.#apply(response)
@@ -205,6 +220,13 @@ class AgentlessConfigurationSource {
205
220
  const etag = response.headers?.etag
206
221
  const value = Array.isArray(etag) ? etag[0] : etag
207
222
  this.#etag = value?.trim() || undefined
223
+
224
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
225
+ log.debug(() => {
226
+ const flagCount = Object.keys(configuration.flags ?? {}).length
227
+ return 'Feature Flags: agentless configuration applied successfully ' +
228
+ `(${flagCount} flag(s), env=${this.#config.env ?? 'unset'})`
229
+ })
208
230
  }
209
231
 
210
232
  /**
@@ -270,10 +292,17 @@ function parseConfiguration (body) {
270
292
  }
271
293
 
272
294
  /**
295
+ * Formats a request error, including the underlying network error code
296
+ * (e.g. ENOTFOUND, ECONNREFUSED, ETIMEDOUT) when available, since "request
297
+ * failed" alone does not tell a customer whether the problem is DNS, a
298
+ * refused connection, a timeout, or something else.
299
+ *
273
300
  * @param {unknown} error
274
301
  */
275
302
  function errorMessage (error) {
276
- return error instanceof Error ? error.message : String(error ?? 'request was not sent')
303
+ if (!(error instanceof Error)) return String(error ?? 'request was not sent')
304
+ const code = /** @type {{ code?: string }} */ (error).code
305
+ return code ? `${error.message} (${code})` : error.message
277
306
  }
278
307
 
279
308
  /**
@@ -23,7 +23,10 @@ function create (config, applyConfiguration) {
23
23
  DD_FEATURE_FLAGS_ENABLED: enabled,
24
24
  } = config.featureFlags
25
25
 
26
- if (!enabled || source !== 'agentless') {
26
+ if (!enabled) return
27
+
28
+ if (source !== 'agentless') {
29
+ log.debug('Feature Flags: configuration source is %s, not agentless; skipping agentless setup', source)
27
30
  return
28
31
  }
29
32
 
@@ -35,11 +38,14 @@ function create (config, applyConfiguration) {
35
38
  }
36
39
 
37
40
  const AgentlessConfigurationSource = require('./agentless_configuration_source')
41
+ const resolvedEndpoint = endpoint(config, baseUrl)
42
+ log.debug('Feature Flags: starting agentless configuration source at %s', redactedUrl(resolvedEndpoint))
38
43
  return new AgentlessConfigurationSource({
39
- endpoint: endpoint(config, baseUrl),
44
+ endpoint: resolvedEndpoint,
40
45
  pollIntervalMs: Math.min(pollIntervalSeconds, MAX_POLL_INTERVAL_SECONDS) * 1000,
41
46
  requestTimeoutMs: requestTimeoutSeconds * 1000,
42
47
  apiKey: hasCustomEndpoint ? undefined : config.DD_API_KEY,
48
+ env: config.env,
43
49
  }, applyConfiguration)
44
50
  } catch (error) {
45
51
  log.error('Unable to configure Feature Flagging configuration source', error)
@@ -85,6 +91,15 @@ function endpoint (config, configuredBaseUrl) {
85
91
  return url
86
92
  }
87
93
 
94
+ /**
95
+ * Drops everything but the origin; a custom base URL may embed a signed token in its path.
96
+ *
97
+ * @param {URL} url
98
+ */
99
+ function redactedUrl (url) {
100
+ return `${url.protocol}//${url.host}`
101
+ }
102
+
88
103
  module.exports = {
89
104
  create,
90
105
  }
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ /**
6
+ * Logs full evaluation details under DD_TRACE_DEBUG, matching EvalMetricsHook's use of `finally`.
7
+ */
8
+ class DebugLoggingHook {
9
+ /**
10
+ * @param {{ flagKey: string }} hookContext - Hook context containing the flag key
11
+ * @param {object} evaluationDetails - Full evaluation details
12
+ */
13
+ finally (hookContext, evaluationDetails) {
14
+ try {
15
+ log.debug('Feature Flags: evaluated %s: %o', hookContext?.flagKey, evaluationDetails)
16
+ } catch (error) {
17
+ try {
18
+ // Defense in depth; the OpenFeature SDK already guards finally hooks.
19
+ log.warn('DebugLoggingHook: error in finally hook: %s', error.message)
20
+ } catch { /* logging failure must not crash evaluation */ }
21
+ }
22
+ }
23
+ }
24
+
25
+ module.exports = DebugLoggingHook
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const PARAMETER = 'dd_evaluation_telemetry_opt_out'
4
+
5
+ /** Gates an owned finally-only telemetry hook without changing evaluation or exposure handling. */
6
+ class EvaluationTelemetryOptOutHook {
7
+ #delegate
8
+ #decisions = new WeakMap()
9
+
10
+ /** @param {import('@openfeature/server-sdk').Hook} delegate */
11
+ constructor (delegate) {
12
+ this.#delegate = delegate
13
+ }
14
+
15
+ /** @param {import('@openfeature/core').HookContext} hookContext */
16
+ before (hookContext) {
17
+ // Hook context identity is stable across stages, including each MultiProvider branch.
18
+ this.#decisions.set(hookContext, isOptedOut(hookContext.context))
19
+ }
20
+
21
+ /** @param {import('@openfeature/core').HookContext} hookContext */
22
+ error (hookContext) {
23
+ // A customer before hook can fail before this hook runs. Capture opt-out before customer error hooks change it.
24
+ if (!this.#decisions.has(hookContext)) {
25
+ this.#decisions.set(hookContext, isOptedOut(hookContext.context))
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @param {import('@openfeature/core').HookContext} hookContext
31
+ * @param {import('@openfeature/core').EvaluationDetails<import('@openfeature/core').FlagValue>} evaluationDetails
32
+ * @param {import('@openfeature/core').HookHints} [hints]
33
+ */
34
+ finally (hookContext, evaluationDetails, hints) {
35
+ const optedOut = this.#decisions.get(hookContext) === true || isOptedOut(hookContext.context)
36
+ this.#decisions.delete(hookContext)
37
+ if (!optedOut) return this.#delegate.finally?.(hookContext, evaluationDetails, hints)
38
+ }
39
+ }
40
+
41
+ /** @param {import('@openfeature/core').EvaluationContext} context */
42
+ function isOptedOut (context) {
43
+ try {
44
+ return context?.[PARAMETER] === true
45
+ } catch {
46
+ // An unreadable attribute is not boolean true and must not change the evaluation result.
47
+ return false
48
+ }
49
+ }
50
+
51
+ module.exports = EvaluationTelemetryOptOutHook
@@ -4,9 +4,12 @@ const { channel } = require('dc-polyfill')
4
4
 
5
5
  const { DatadogNodeServerProvider } = require('../../../../vendor/dist/@datadog/openfeature-node-server')
6
6
  const log = require('../log')
7
+ const { debugChannel } = require('../log/channels')
7
8
  const configurationSource = require('./configuration_source')
8
9
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
10
+ const DebugLoggingHook = require('./debug-logging-hook')
9
11
  const EvalMetricsHook = require('./eval-metrics-hook')
12
+ const EvaluationTelemetryOptOutHook = require('./evaluation-telemetry-opt-out-hook')
10
13
  const SpanEnrichmentHook = require('./span-enrichment-hook')
11
14
  const FlagEvalEVPHook = require('./writers/flag-eval-evp-hook')
12
15
 
@@ -15,6 +18,9 @@ const FlagEvalEVPHook = require('./writers/flag-eval-evp-hook')
15
18
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
16
19
  */
17
20
  class FlaggingProvider extends DatadogNodeServerProvider {
21
+ // Track this wrapper's logging lifecycle without reading the upstream provider's private state.
22
+ #waitingForConfiguration = false
23
+
18
24
  /** @type {SpanEnrichmentHook | undefined} */
19
25
  #spanEnrichmentHook
20
26
 
@@ -34,14 +40,25 @@ class FlaggingProvider extends DatadogNodeServerProvider {
34
40
  initializationTimeoutMs: config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS,
35
41
  })
36
42
 
43
+ const enableOptOut =
44
+ config.featureFlags.DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED === true
45
+ /** @param {import('@openfeature/server-sdk').Hook} hook */
46
+ const telemetryHook = hook => enableOptOut ? new EvaluationTelemetryOptOutHook(hook) : hook
47
+
37
48
  if (config.DD_METRICS_OTEL_ENABLED === true) {
38
- this.hooks.push(new EvalMetricsHook(config))
49
+ this.hooks.push(telemetryHook(new EvalMetricsHook(config)))
50
+ } else {
51
+ log.debug('Feature Flags: evaluation metrics disabled; set %s=true to enable', 'DD_METRICS_OTEL_ENABLED')
52
+ }
53
+
54
+ if (config.debug && debugChannel.hasSubscribers) {
55
+ this.hooks.push(new DebugLoggingHook())
39
56
  }
40
57
 
41
58
  if (config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED) {
42
59
  this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
43
60
  // @ts-expect-error The upstream constructor always initializes its optional hooks property.
44
- this.hooks.push(this.#spanEnrichmentHook)
61
+ this.hooks.push(telemetryHook(this.#spanEnrichmentHook))
45
62
  log.info('%s span enrichment enabled', this.constructor.name)
46
63
  } else {
47
64
  log.info('%s span enrichment disabled', this.constructor.name)
@@ -52,7 +69,7 @@ class FlaggingProvider extends DatadogNodeServerProvider {
52
69
 
53
70
  if (config.featureFlags?.DD_FLAGGING_EVALUATION_COUNTS_ENABLED !== false) {
54
71
  this.#flagEvalEVPHook = new FlagEvalEVPHook(config)
55
- this.hooks.push(this.#flagEvalEVPHook)
72
+ this.hooks.push(telemetryHook(this.#flagEvalEVPHook))
56
73
  }
57
74
 
58
75
  this.#configurationSource = configurationSource.create(config, this.setConfiguration.bind(this))
@@ -64,6 +81,9 @@ class FlaggingProvider extends DatadogNodeServerProvider {
64
81
  * @returns {Promise<void>}
65
82
  */
66
83
  initialize (context) {
84
+ log.debug('Feature Flags: waiting for provider initialization...')
85
+
86
+ this.#waitingForConfiguration = !this.getConfiguration()
67
87
  const promise = super.initialize(context)
68
88
 
69
89
  // `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
@@ -72,9 +92,45 @@ class FlaggingProvider extends DatadogNodeServerProvider {
72
92
  // TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
73
93
  this.initController?.timeoutId?.unref?.()
74
94
 
95
+ if (this.getConfiguration()) this.#logInitialConfiguration()
96
+
75
97
  return promise
76
98
  }
77
99
 
100
+ /**
101
+ * @param {Parameters<DatadogNodeServerProvider['setConfiguration']>[0]} configuration
102
+ */
103
+ setConfiguration (configuration) {
104
+ const wasWaiting = this.#waitingForConfiguration
105
+ super.setConfiguration(configuration)
106
+ this.#waitingForConfiguration = false
107
+ if (wasWaiting && configuration != null) this.#logInitialConfiguration()
108
+ }
109
+
110
+ /**
111
+ * @param {unknown} error
112
+ */
113
+ setError (error) {
114
+ const wasWaiting = this.#waitingForConfiguration
115
+ super.setError(error)
116
+ this.#waitingForConfiguration = false
117
+ if (!wasWaiting) return
118
+
119
+ try {
120
+ // Avoid inflating telemetry volume for routine initialization timeouts.
121
+ log.errorWithoutTelemetry(
122
+ 'Feature Flags: provider failed to initialize: %s',
123
+ error instanceof Error ? error.message : String(error)
124
+ )
125
+ } catch { /* logging failure must not affect initialization */ }
126
+ }
127
+
128
+ #logInitialConfiguration () {
129
+ try {
130
+ log.debug('Feature Flags: initial configuration available')
131
+ } catch { /* logging failure must not affect initialization */ }
132
+ }
133
+
78
134
  /**
79
135
  * Called when the provider is shut down.
80
136
  * Cleans up resources including channel subscriptions.
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../log')
3
4
  const RemoteConfigCapabilities = require('../remote_config/capabilities')
4
5
 
5
6
  /**
@@ -12,6 +13,8 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
12
13
  function enable (rc, getOpenfeatureProxy, subscribe) {
13
14
  if (!subscribe) return
14
15
 
16
+ log.debug('Feature Flags: starting remote_config configuration source (Agent Remote Configuration)')
17
+
15
18
  rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
16
19
 
17
20
  /**
@@ -21,7 +24,13 @@ function enable (rc, getOpenfeatureProxy, subscribe) {
21
24
  const updateConfiguration = (action, conf) => {
22
25
  if (action === 'apply' || action === 'modify') {
23
26
  getOpenfeatureProxy().setConfiguration(conf)
27
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
28
+ log.debug(() => {
29
+ const flagCount = Object.keys(conf?.flags ?? {}).length
30
+ return `Feature Flags: remote_config configuration ${action} applied successfully (${flagCount} flag(s))`
31
+ })
24
32
  } else if (action === 'unapply') {
33
+ log.debug('Feature Flags: remote_config configuration removed')
25
34
  getOpenfeatureProxy().setConfiguration(undefined)
26
35
  }
27
36
  }
@@ -16,6 +16,18 @@ const ROUTE_DISCOVERY_COOLDOWN_MS = 60_000
16
16
  const REQUIRED_LOCAL_HEADERS = [EVP_ORIGIN_HEADER_NAME, EVP_ORIGIN_VERSION_HEADER_NAME]
17
17
  const STOP_NOOP = () => {}
18
18
 
19
+ /**
20
+ * @typedef {(enabled: boolean, route?: import('./flag-evaluations').FlagEvaluationRoute) => void} DeliverySubscriber
21
+ */
22
+ /**
23
+ * @typedef {object} DeliveryStrategy
24
+ * @property {Set<{notify: DeliverySubscriber}>} subscribers
25
+ * @property {() => void} stop
26
+ * @property {[boolean, import('./flag-evaluations').FlagEvaluationRoute?]} [result]
27
+ */
28
+ /** @type {WeakMap<import('../../config/config-base'), DeliveryStrategy>} */
29
+ const deliveryStrategies = new WeakMap()
30
+
19
31
  let missingRouteWarningLogged = false
20
32
 
21
33
  /**
@@ -167,18 +179,45 @@ function setAgentlessStrategy (config, setWriterEnabledValue) {
167
179
  }
168
180
 
169
181
  /**
170
- * Applies one event-delivery strategy that can be shared by all Feature Flags writers.
182
+ * Subscribes to the delivery strategy owned by this tracer configuration.
183
+ * The provider and exposure module receive the same configuration object from
184
+ * the tracer. Share their route transitions, but never share credentials across
185
+ * distinct configurations. The last subscriber owns discovery cleanup.
171
186
  *
172
187
  * @param {import('../../config/config-base')} config - Tracer configuration object
173
188
  * @param {(enabled: boolean, route?: import('./flag-evaluations').FlagEvaluationRoute) => void} setWriterEnabledValue
174
189
  * Callback to set the writer enabled state
175
190
  */
176
191
  function setEventDeliveryStrategy (config, setWriterEnabledValue) {
177
- if (config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless') {
178
- return setAgentlessStrategy(config, setWriterEnabledValue)
192
+ let strategy = deliveryStrategies.get(config)
193
+ const subscriber = { notify: setWriterEnabledValue }
194
+
195
+ if (strategy) {
196
+ strategy.subscribers.add(subscriber)
197
+ if (strategy.result) setWriterEnabledValue(...strategy.result)
198
+ } else {
199
+ strategy = { subscribers: new Set([subscriber]), stop: STOP_NOOP }
200
+ deliveryStrategies.set(config, strategy)
201
+ /** @type {DeliverySubscriber} */
202
+ const notify = (enabled, route) => {
203
+ // A Remote Config discovery already in flight cannot be canceled. Ignore
204
+ // its result after shutdown, including when the same config is reused.
205
+ if (deliveryStrategies.get(config) !== strategy) return
206
+ strategy.result = route === undefined ? [enabled] : [enabled, route]
207
+ for (const subscriber of strategy.subscribers) {
208
+ subscriber.notify(...strategy.result)
209
+ }
210
+ }
211
+ strategy.stop = config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless'
212
+ ? setAgentlessStrategy(config, notify)
213
+ : setAgentStrategy(config, notify)
179
214
  }
180
215
 
181
- return setAgentStrategy(config, setWriterEnabledValue)
216
+ return () => {
217
+ if (!strategy.subscribers.delete(subscriber) || strategy.subscribers.size !== 0) return
218
+ deliveryStrategies.delete(config)
219
+ strategy.stop()
220
+ }
182
221
  }
183
222
 
184
223
  module.exports = {
@@ -52,7 +52,7 @@ function encodeHrTime (hrTime) {
52
52
  * OtlpTransformer transforms log records to OTLP format.
53
53
  *
54
54
  * This implementation follows the OTLP Logs v1.7.0 Data Model specification:
55
- * https://opentelemetry.io/docs/specs/otlp/#log-data-model
55
+ * https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/logs/v1/logs.proto
56
56
  *
57
57
  * @class OtlpTransformer
58
58
  * @augments OtlpTransformerBase
@@ -18,7 +18,7 @@ const AGGREGATION_TEMPORALITY_CUMULATIVE = protoAggregationTemporality.values.AG
18
18
  * OtlpTransformer transforms metrics to OTLP format.
19
19
  *
20
20
  * This implementation follows the OTLP Metrics v1.7.0 Data Model specification:
21
- * https://opentelemetry.io/docs/specs/otlp/#metrics-data-model
21
+ * https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/metrics/v1/metrics.proto
22
22
  *
23
23
  * @class OtlpTransformer
24
24
  * @augments OtlpTransformerBase
@@ -162,8 +162,7 @@ class PeriodicMetricReader {
162
162
  addBatchObservableCallback (callback, observables) {
163
163
  if (typeof callback !== 'function') return
164
164
  const instruments = new Set(observables?.filter(isObservableInstrument))
165
- if (instruments.size === 0) return
166
- if (this.#findBatchCallback(callback, instruments) !== -1) return
165
+ if (instruments.size === 0 || this.#findBatchCallback(callback, instruments) !== -1) return
167
166
  this.#batchCallbacks.push({ callback, instruments })
168
167
  }
169
168
 
@@ -1,4 +1,4 @@
1
- // Vendored from: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/logs/v1/common.proto
1
+ // Vendored from: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/common/v1/common.proto
2
2
  // Copyright 2019, OpenTelemetry Authors
3
3
  //
4
4
  // Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,7 @@ function buildResourceAttributes (config) {
30
30
  const resourceAttributes = {
31
31
  'service.name': config.service,
32
32
  'service.version': config.version,
33
- 'deployment.environment': config.env,
33
+ 'deployment.environment.name': config.env,
34
34
  ...tags,
35
35
  }
36
36
 
@@ -84,7 +84,7 @@ const DD_ERROR_META_KEYS = new Set(['error.message'])
84
84
  * OtlpTraceTransformer transforms DD-formatted spans to OTLP trace JSON format.
85
85
  *
86
86
  * This implementation follows the OTLP trace data model:
87
- * https://opentelemetry.io/docs/specs/otlp/#trace-data-model
87
+ * https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/trace/v1/trace.proto
88
88
  *
89
89
  * It receives DD-formatted spans (from span_format.js) and produces
90
90
  * an ExportTraceServiceRequest serialized as JSON (http/json protocol only).
@@ -168,8 +168,8 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
168
168
 
169
169
  return {
170
170
  traceId: span.trace_id.toTraceIdHex(traceIdHigh).padStart(32, '0'),
171
- spanId: this.#idToBytes(span.span_id, 8),
172
- parentSpanId: (parentId && !parentId.equals(ZERO_ID)) ? this.#idToBytes(parentId, 8) : undefined,
171
+ spanId: this.#spanIdToHex(span.span_id),
172
+ parentSpanId: parentId && !parentId.equals(ZERO_ID) ? this.#spanIdToHex(parentId) : undefined,
173
173
  traceState: span.trace_state,
174
174
  name: span.resource,
175
175
  kind: this.#mapSpanKind(span.meta?.['span.kind']),
@@ -338,26 +338,9 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
338
338
  }
339
339
  }
340
340
 
341
- /**
342
- * Converts a DD Identifier object to a hex-encoded string of the specified byte length.
343
- * Pads with leading zeros if the identifier buffer is shorter than the target.
344
- * Per the OTLP http/json spec, trace-ids and span-ids must be hex-encoded strings.
345
- *
346
- * @param {Identifier} identifier - DD Identifier
347
- * @param {number} targetLength - Target byte length (16 for trace ID, 8 for span ID)
348
- */
349
- #idToBytes (identifier, targetLength) {
350
- const buffer = identifier.toBuffer()
351
- if (buffer.length === targetLength) {
352
- return Buffer.from(buffer).toString('hex')
353
- }
354
- if (buffer.length > targetLength) {
355
- return Buffer.from(buffer.slice(buffer.length - targetLength)).toString('hex')
356
- }
357
- // Pad with leading zeros to reach target length.
358
- const result = Buffer.alloc(targetLength)
359
- Buffer.from(buffer).copy(result, targetLength - buffer.length)
360
- return result.toString('hex')
341
+ /** @param {Identifier} identifier */
342
+ #spanIdToHex (identifier) {
343
+ return identifier.toString(16).slice(-16)
361
344
  }
362
345
 
363
346
  /**
@@ -38,7 +38,7 @@ const id = require('../../id')
38
38
  const DatadogSpanContext = require('../span_context')
39
39
  const log = require('../../log')
40
40
  const tags = require('../../../../../ext/tags')
41
- const { getConfiguredEnvName } = require('../../config/helper')
41
+ const { getValidatedConfiguredEnvName } = require('../../config/helper')
42
42
  const { setAllBaggageItems, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
43
43
  const { hasTraceSourcePropagationTag } = require('../../standalone/tracesource')
44
44
  const telemetryMetrics = require('../../telemetry/metrics')
@@ -134,8 +134,7 @@ function isOptionalDatadogTraceStateField (key, injection) {
134
134
  * @returns {DatadogSpanContext | undefined}
135
135
  */
136
136
  function extractGenericContext (traceId, spanId, radix) {
137
- if (!traceId || invalidSegment.test(traceId)) return
138
- if (!spanId) return
137
+ if (!traceId || invalidSegment.test(traceId) || !spanId) return
139
138
 
140
139
  return new DatadogSpanContext({
141
140
  traceId: id(traceId, radix),
@@ -305,13 +304,25 @@ class TextMapPropagator {
305
304
  /** @type {string[] | undefined} Source array that `#baggageTagKeysSet` was built from. */
306
305
  #baggageTagKeysSetSource
307
306
 
308
- /** @type {import('../../config')} */
307
+ /** @type {ReturnType<import('../../config')>} */
309
308
  #config
310
309
 
311
310
  /** @type {typeof extractB3SingleContext | typeof extractB3MultiContext} */
312
311
  #extractB3Context
313
312
 
314
- /** @param {import('../../config')} config */
313
+ /** Whether the `b3` injection style resolves to single-header propagation. */
314
+ #b3InjectIsSingleHeader
315
+
316
+ /** Cached origin used to resolve the v5 `b3` injection style. */
317
+ #b3InjectOrigin
318
+
319
+ /** Cached state of the v5 experimental B3 setting. */
320
+ #b3ExperimentalEnabled
321
+
322
+ /** Cached origin of the shared v5 propagation style. */
323
+ #b3SharedOrigin
324
+
325
+ /** @param {ReturnType<import('../../config')>} config */
315
326
  constructor (config) {
316
327
  this.#config = config
317
328
 
@@ -319,16 +330,44 @@ class TextMapPropagator {
319
330
  // expect single, legacy `DD_TRACE_PROPAGATION_STYLE` callers expect multi.
320
331
  /* istanbul ignore else: v5 fallback */
321
332
  if (DD_MAJOR >= 6) {
333
+ this.#b3InjectIsSingleHeader = true
322
334
  this.#extractB3Context = extractB3SingleContext
323
335
  } else {
324
- const envName = getConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
336
+ const envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
325
337
  // eslint-disable-next-line eslint-rules/eslint-env-aliases
326
338
  this.#extractB3Context = envName === 'OTEL_PROPAGATORS'
327
339
  ? extractB3SingleContext
328
340
  : extractB3MultiContext
341
+
342
+ this.#refreshB3InjectionStyle()
329
343
  }
330
344
  }
331
345
 
346
+ #refreshB3InjectionStyle () {
347
+ const origin = this.#config.getOrigin?.('tracePropagationStyle.inject') ?? 'default'
348
+ const experimentalEnabled = this.#config.experimental?.b3 === true
349
+ const sharedOrigin = origin === 'calculated'
350
+ ? this.#config.getOrigin?.('DD_TRACE_PROPAGATION_STYLE') ?? 'default'
351
+ : undefined
352
+ if (origin === this.#b3InjectOrigin &&
353
+ experimentalEnabled === this.#b3ExperimentalEnabled &&
354
+ sharedOrigin === this.#b3SharedOrigin) return
355
+
356
+ let envName
357
+ if (origin === 'calculated' && !experimentalEnabled && sharedOrigin !== 'remote_config') {
358
+ envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
359
+ } else if (origin === 'env_var' ||
360
+ origin === 'local_stable_config' ||
361
+ origin === 'fleet_stable_config') {
362
+ envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE_INJECT')
363
+ }
364
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
365
+ this.#b3InjectIsSingleHeader = envName === 'OTEL_PROPAGATORS'
366
+ this.#b3InjectOrigin = origin
367
+ this.#b3ExperimentalEnabled = experimentalEnabled
368
+ this.#b3SharedOrigin = sharedOrigin
369
+ }
370
+
332
371
  /**
333
372
  * Returns a `Set` view of `#config.baggageTagKeys` that is rebuilt only
334
373
  * when the source array reference changes. Avoids an `O(n)` `Set` alloc
@@ -574,9 +613,8 @@ class TextMapPropagator {
574
613
  * @returns {Record<string, string> | undefined}
575
614
  */
576
615
  #injectB3MultipleHeaders (spanContext, carrier) {
577
- // v5 also accepts the legacy `'b3'` spelling for multi; v6+ routes `'b3'` to single-header.
578
616
  const hasB3multi = this.#hasPropagationStyle('inject', 'b3multi') ||
579
- (DD_MAJOR < 6 && this.#hasPropagationStyle('inject', 'b3'))
617
+ (this.#hasPropagationStyle('inject', 'b3') && !this.#usesB3SingleHeaderInjection())
580
618
  if (!hasB3multi) return
581
619
 
582
620
  carrier ??= {}
@@ -603,7 +641,7 @@ class TextMapPropagator {
603
641
  #injectB3SingleHeader (spanContext, carrier) {
604
642
  // v6+ keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
605
643
  const hasB3SingleHeader = this.#hasPropagationStyle('inject', 'b3 single header') ||
606
- (DD_MAJOR >= 6 && this.#hasPropagationStyle('inject', 'b3'))
644
+ (this.#hasPropagationStyle('inject', 'b3') && this.#usesB3SingleHeaderInjection())
607
645
  if (!hasB3SingleHeader) return
608
646
 
609
647
  carrier ??= {}
@@ -620,6 +658,11 @@ class TextMapPropagator {
620
658
  return carrier
621
659
  }
622
660
 
661
+ #usesB3SingleHeaderInjection () {
662
+ if (DD_MAJOR < 6) this.#refreshB3InjectionStyle()
663
+ return this.#b3InjectIsSingleHeader
664
+ }
665
+
623
666
  /**
624
667
  * @param {DatadogSpanContext} spanContext
625
668
  * @param {Record<string, string> | undefined} carrier
@@ -982,11 +1025,8 @@ class TextMapPropagator {
982
1025
  const traceparent = { version }
983
1026
  // W3C Trace Context §3.3.1.1: multiple tracestate fields MUST be combined per RFC 7230 §3.2.2.
984
1027
  const tracestate = TraceState.fromString(readTracestate(carrier))
985
- if (invalidSegment.test(traceId)) return
986
- if (invalidSegment.test(spanId)) return
987
-
988
1028
  // Version ff is considered invalid
989
- if (version === 'ff') return
1029
+ if (invalidSegment.test(traceId) || invalidSegment.test(spanId) || version === 'ff') return
990
1030
 
991
1031
  // Version 00 should have no tail, but future versions may
992
1032
  if (tail && version === '00') return