dd-trace 5.104.0 → 5.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -0,0 +1,143 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+ const log = require('../log')
5
+ const { SpanEnrichmentState } = require('./span-enrichment')
6
+
7
+ const finishCh = channel('dd-trace:span:finish')
8
+
9
+ /**
10
+ * OpenFeature hook that enriches APM spans with feature flag evaluation data.
11
+ *
12
+ * Implements the OpenFeature `finally` hook interface to capture flag evaluations
13
+ * and add span tags for observability. Tags are accumulated during the span's
14
+ * lifetime and applied when the root span finishes.
15
+ *
16
+ * Span tags added:
17
+ * - `ffe_flags_enc`: Base64 delta-varint encoded serial IDs
18
+ * - `ffe_subjects_enc`: JSON dict of SHA256(targeting_key) → encoded serial IDs
19
+ * - `ffe_runtime_defaults`: JSON dict of flag_key → default value string
20
+ */
21
+ class SpanEnrichmentHook {
22
+ #tracer
23
+ /** @type {WeakMap<object, SpanEnrichmentState>} */
24
+ #spanStates = new WeakMap()
25
+
26
+ /**
27
+ * Handler for span finish channel. Applies accumulated tags to the span.
28
+ * Arrow function to preserve `this` binding for channel subscription.
29
+ *
30
+ * @param {object} span - The span that is finishing
31
+ */
32
+ #onSpanFinish = (span) => {
33
+ const state = this.#spanStates.get(span)
34
+ if (!state || !state.hasData()) return
35
+
36
+ try {
37
+ const tags = state.toSpanTags()
38
+
39
+ for (const [key, value] of Object.entries(tags)) {
40
+ if (value) {
41
+ span.setTag(key, value)
42
+ }
43
+ }
44
+ } catch (err) {
45
+ log.warn('SpanEnrichmentHook: error applying span tags: %s', err.message)
46
+ } finally {
47
+ this.#spanStates.delete(span)
48
+ }
49
+ }
50
+
51
+ /**
52
+ * @param {import('../tracer')} tracer - Datadog tracer instance
53
+ */
54
+ constructor (tracer) {
55
+ this.#tracer = tracer
56
+ finishCh.subscribe(this.#onSpanFinish)
57
+ }
58
+
59
+ /**
60
+ * Called by the OpenFeature SDK after every flag evaluation (success or error).
61
+ *
62
+ * @param {object} hookContext - Hook context containing the flag key and evaluation context
63
+ * @param {string} hookContext.flagKey - The flag key being evaluated
64
+ * @param {object} [hookContext.context] - Evaluation context
65
+ * @param {string} [hookContext.context.targetingKey] - Targeting key
66
+ * @param {object} evaluationDetails - Full evaluation details including flag metadata
67
+ * @param {object} [evaluationDetails.flagMetadata] - Metadata from the provider
68
+ * @param {number} [evaluationDetails.flagMetadata.__dd_split_serial_id] - Serial ID from UFC split
69
+ * @param {boolean} [evaluationDetails.flagMetadata.__dd_do_log] - Whether to log subject
70
+ * @param {string} [evaluationDetails.variant] - Variant key if flag was found in UFC
71
+ * @param {boolean|string|number|object} [evaluationDetails.value] - Evaluated value
72
+ * @returns {void}
73
+ */
74
+ finally (hookContext, evaluationDetails) {
75
+ try {
76
+ const rootSpan = this._getRootSpan()
77
+ if (!rootSpan) return
78
+
79
+ const state = this._getOrCreateState(rootSpan)
80
+ const { flagKey, context } = hookContext || {}
81
+ const { flagMetadata, variant, value } = evaluationDetails || {}
82
+
83
+ const serialId = flagMetadata?.__dd_split_serial_id
84
+ const doLog = flagMetadata?.__dd_do_log ?? false
85
+ const targetingKey = context?.targetingKey
86
+
87
+ if (serialId != null) {
88
+ state.addSerialId(serialId)
89
+
90
+ if (doLog && targetingKey) {
91
+ state.addSubject(targetingKey, serialId)
92
+ }
93
+ } else if (variant === undefined) {
94
+ state.addDefault(flagKey, value)
95
+ }
96
+ } catch (err) {
97
+ log.warn('SpanEnrichmentHook: error in finally hook: %s', err.message)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get the root span for the current trace context.
103
+ * The root span is always the first span in trace.started since spans
104
+ * are added in creation order and the root is created first.
105
+ *
106
+ * @returns {object|null} The root span, or null if no active span
107
+ * @private
108
+ */
109
+ _getRootSpan () {
110
+ const span = this.#tracer.scope().active()
111
+ if (!span) return null
112
+
113
+ const trace = span.context()._trace
114
+
115
+ return trace?.started?.[0] ?? span
116
+ }
117
+
118
+ /**
119
+ * Get or create enrichment state for a span.
120
+ *
121
+ * @param {object} span - The span to get state for
122
+ * @returns {SpanEnrichmentState} The enrichment state
123
+ * @private
124
+ */
125
+ _getOrCreateState (span) {
126
+ let state = this.#spanStates.get(span)
127
+ if (!state) {
128
+ state = new SpanEnrichmentState()
129
+ this.#spanStates.set(span, state)
130
+ }
131
+ return state
132
+ }
133
+
134
+ /**
135
+ * Cleanup method to unsubscribe from channels.
136
+ * Should be called when the provider is shut down.
137
+ */
138
+ destroy () {
139
+ finishCh.unsubscribe(this.#onSpanFinish)
140
+ }
141
+ }
142
+
143
+ module.exports = SpanEnrichmentHook
@@ -0,0 +1,149 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ const { encodeDeltaVarint, hashTargetingKey } = require('./encoding')
6
+
7
+ const MAX_SERIAL_IDS = 200
8
+ const MAX_SUBJECTS = 10
9
+ const MAX_EXPERIMENTS_PER_SUBJECT = 20
10
+ const MAX_DEFAULTS = 5
11
+ const MAX_DEFAULT_VALUE_LENGTH = 64
12
+
13
+ /**
14
+ * Manages feature flag enrichment state for a single root span.
15
+ * Accumulates serial IDs, subjects, and defaults throughout the span's lifetime.
16
+ */
17
+ class SpanEnrichmentState {
18
+ constructor () {
19
+ /** @type {Set<number>} */
20
+ this._serialIds = new Set()
21
+
22
+ /** @type {Map<string, Set<number>>} hashed targeting key -> serial IDs */
23
+ this._subjects = new Map()
24
+
25
+ /** @type {Map<string, string>} flag key -> runtime default value */
26
+ this._defaults = new Map()
27
+ }
28
+
29
+ /**
30
+ * Add a serial ID from a flag evaluation.
31
+ *
32
+ * @param {number} serialId - The serial ID to add
33
+ * @returns {boolean} True if added, false if limit reached
34
+ */
35
+ addSerialId (serialId) {
36
+ if (this._serialIds.size >= MAX_SERIAL_IDS) {
37
+ log.debug('SpanEnrichment: MAX_SERIAL_IDS limit (%d) reached, dropping serialId %d', MAX_SERIAL_IDS, serialId)
38
+ return false
39
+ }
40
+ this._serialIds.add(serialId)
41
+ return true
42
+ }
43
+
44
+ /**
45
+ * Add a subject (targeting key) with its associated serial ID.
46
+ * Only called when doLog=true.
47
+ *
48
+ * @param {string} targetingKey - The targeting key (will be hashed)
49
+ * @param {number} serialId - The serial ID associated with this evaluation
50
+ * @returns {boolean} True if added, false if limit reached
51
+ */
52
+ addSubject (targetingKey, serialId) {
53
+ const hashedKey = hashTargetingKey(targetingKey)
54
+
55
+ if (this._subjects.has(hashedKey)) {
56
+ const subjectIds = this._subjects.get(hashedKey)
57
+ if (subjectIds.size >= MAX_EXPERIMENTS_PER_SUBJECT) {
58
+ log.debug('SpanEnrichment: MAX_EXPERIMENTS_PER_SUBJECT limit (%d) reached for subject',
59
+ MAX_EXPERIMENTS_PER_SUBJECT)
60
+ return false
61
+ }
62
+ subjectIds.add(serialId)
63
+ return true
64
+ }
65
+
66
+ if (this._subjects.size >= MAX_SUBJECTS) {
67
+ log.debug('SpanEnrichment: MAX_SUBJECTS limit (%d) reached, dropping subject', MAX_SUBJECTS)
68
+ return false
69
+ }
70
+
71
+ this._subjects.set(hashedKey, new Set([serialId]))
72
+ return true
73
+ }
74
+
75
+ /**
76
+ * Add a default fallback for a flag not found in UFC.
77
+ *
78
+ * @param {string} flagKey - The flag key
79
+ * @param {boolean|string|number|object} defaultValue - The default value used
80
+ * @returns {boolean} True if added, false if limit reached
81
+ */
82
+ addDefault (flagKey, defaultValue) {
83
+ if (this._defaults.has(flagKey)) {
84
+ return true
85
+ }
86
+
87
+ if (this._defaults.size >= MAX_DEFAULTS) {
88
+ log.debug('SpanEnrichment: MAX_DEFAULTS limit (%d) reached, dropping flag %s', MAX_DEFAULTS, flagKey)
89
+ return false
90
+ }
91
+
92
+ let valueStr = typeof defaultValue === 'object' && defaultValue !== null
93
+ ? JSON.stringify(defaultValue)
94
+ : String(defaultValue)
95
+
96
+ if (valueStr.length > MAX_DEFAULT_VALUE_LENGTH) {
97
+ valueStr = valueStr.slice(0, MAX_DEFAULT_VALUE_LENGTH)
98
+ }
99
+
100
+ this._defaults.set(flagKey, valueStr)
101
+ return true
102
+ }
103
+
104
+ /**
105
+ * Check if there is any enrichment data to add to the span.
106
+ * Note: _subjects is not checked because addSubject() is never called without first
107
+ * calling addSerialId(), so _subjects having data necessitates _serialIds having data.
108
+ *
109
+ * @returns {boolean} True if there is data to add
110
+ */
111
+ hasData () {
112
+ return this._serialIds.size > 0 || this._defaults.size > 0
113
+ }
114
+
115
+ /**
116
+ * Convert accumulated state to span tags.
117
+ *
118
+ * @returns {object} Object with ffe_flags_enc, ffe_subjects_enc, and ffe_runtime_defaults tags
119
+ */
120
+ toSpanTags () {
121
+ const tags = {}
122
+
123
+ if (this._serialIds.size > 0) {
124
+ tags.ffe_flags_enc = encodeDeltaVarint(this._serialIds)
125
+ }
126
+
127
+ if (this._subjects.size > 0) {
128
+ const subjectsObj = Object.fromEntries(
129
+ [...this._subjects].map(([key, ids]) => [key, encodeDeltaVarint(ids)])
130
+ )
131
+ tags.ffe_subjects_enc = JSON.stringify(subjectsObj)
132
+ }
133
+
134
+ if (this._defaults.size > 0) {
135
+ tags.ffe_runtime_defaults = JSON.stringify(Object.fromEntries(this._defaults))
136
+ }
137
+
138
+ return tags
139
+ }
140
+ }
141
+
142
+ module.exports = {
143
+ SpanEnrichmentState,
144
+ MAX_SERIAL_IDS,
145
+ MAX_SUBJECTS,
146
+ MAX_EXPERIMENTS_PER_SUBJECT,
147
+ MAX_DEFAULTS,
148
+ MAX_DEFAULT_VALUE_LENGTH,
149
+ }
@@ -7,6 +7,7 @@ const { timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/co
7
7
  const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../constants')
8
8
  const DatadogSpanContext = require('../opentracing/span_context')
9
9
  const TraceState = require('../opentracing/propagation/tracestate')
10
+ const { DD_MAJOR } = require('../../../../version')
10
11
 
11
12
  const id = require('../id')
12
13
 
@@ -176,8 +177,8 @@ function setOtelAttributes (ddSpan, attributes) {
176
177
  function addOtelLink (ddSpan, link, attrs) {
177
178
  if (!isWritable(ddSpan) || !link) return
178
179
 
179
- // TODO: Drop the (context, attrs) form in v6.0.0.
180
- const { context, attributes } = isOtelLink(link)
180
+ // v5 still accepts the legacy `addLink(context, attrs)` shape; v6 only takes `addLink(otel.Link)`.
181
+ const { context, attributes } = isOtelLink(link) || DD_MAJOR >= 6
181
182
  ? link
182
183
  : { context: link, attributes: attrs ?? {} }
183
184
 
@@ -230,7 +231,7 @@ function recordException (ddSpan, exception, timeInput) {
230
231
  [ERROR_TYPE]: exception.name,
231
232
  [ERROR_MESSAGE]: exception.message,
232
233
  [ERROR_STACK]: exception.stack,
233
- [IGNORE_OTEL_ERROR]: ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true,
234
+ [IGNORE_OTEL_ERROR]: ddSpan.context().getTag(IGNORE_OTEL_ERROR) ?? true,
234
235
  })
235
236
 
236
237
  const attributes = {}
@@ -216,7 +216,7 @@ class Span extends BridgeSpanBase {
216
216
  'ptr.hash': ptrHash,
217
217
  'link.kind': 'span-pointer',
218
218
  }
219
- return this.addLink(zeroContext, attributes)
219
+ return this.addLink({ context: zeroContext, attributes })
220
220
  }
221
221
 
222
222
  /**
@@ -11,20 +11,31 @@ class LogPropagator {
11
11
  inject (spanContext, carrier) {
12
12
  if (!carrier) return
13
13
 
14
- carrier.dd = {}
14
+ const dd = {}
15
+ let hasField = false
15
16
 
16
17
  if (spanContext) {
17
- carrier.dd.trace_id = this._config.traceId128BitGenerationEnabled &&
18
+ dd.trace_id = this._config.traceId128BitGenerationEnabled &&
18
19
  this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']
19
20
  ? spanContext.toTraceId(true)
20
21
  : spanContext.toTraceId()
21
-
22
- carrier.dd.span_id = spanContext.toSpanId()
22
+ dd.span_id = spanContext.toSpanId()
23
+ hasField = true
24
+ }
25
+ if (this._config.service) {
26
+ dd.service = this._config.service
27
+ hasField = true
28
+ }
29
+ if (this._config.version) {
30
+ dd.version = this._config.version
31
+ hasField = true
32
+ }
33
+ if (this._config.env) {
34
+ dd.env = this._config.env
35
+ hasField = true
23
36
  }
24
37
 
25
- if (this._config.service) carrier.dd.service = this._config.service
26
- if (this._config.version) carrier.dd.version = this._config.version
27
- if (this._config.env) carrier.dd.env = this._config.env
38
+ if (hasField) carrier.dd = dd
28
39
  }
29
40
 
30
41
  extract (carrier) {
@@ -35,7 +35,6 @@ const b3FlagsKey = 'x-b3-flags'
35
35
  const b3HeaderKey = 'b3'
36
36
  const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog'
37
37
  const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i
38
- const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
39
38
  // W3C Baggage key grammar: key = token (RFC 7230).
40
39
  // Spec (up-to-date): "Propagation format for distributed context: Baggage" §3.3.1
41
40
  // https://www.w3.org/TR/baggage/#header-content
@@ -56,6 +55,15 @@ const ddKeys = [traceKey, spanKey, samplingKey, originKey]
56
55
  const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
57
56
  const w3cKeys = [traceparentKey, tracestateKey]
58
57
  const logKeys = [...ddKeys, ...b3Keys, ...w3cKeys]
58
+ // Dispatch table for `_extractSpanContext`. `'b3'` resolves to the matching
59
+ // single/multi extractor per instance — see `#b3MethodName` — so it is not in
60
+ // this table. `'baggage'` is consumed by `_extractBaggageItems`, not the loop.
61
+ const EXTRACT_STYLE_METHODS = new Map([
62
+ ['datadog', '_extractDatadogContext'],
63
+ ['tracecontext', '_extractTraceparentContext'],
64
+ ['b3 single header', '_extractB3SingleContext'],
65
+ ['b3multi', '_extractB3MultiContext'],
66
+ ])
59
67
  // Origin value in tracestate replaces '~', ',' and ';' with '_"
60
68
  const tracestateOriginFilter = /[^\x20-\x2B\x2D-\x3A\x3C-\x7D]/g
61
69
  // Tag keys in tracestate replace ' ', ',' and '=' with '_'
@@ -68,27 +76,29 @@ const hex16 = /^[0-9A-Fa-f]{16}$/
68
76
  const percentByte = /%([0-9A-Fa-f]{2})/g
69
77
 
70
78
  class TextMapPropagator {
71
- #extractB3Context
72
-
73
79
  /** @type {Set<string> | undefined} Cached `Set` view of `_config.baggageTagKeys`. */
74
80
  #baggageTagKeysSet
75
81
 
76
82
  /** @type {string[] | undefined} Source array that `#baggageTagKeysSet` was built from. */
77
83
  #baggageTagKeysSetSource
78
84
 
85
+ /** @type {'_extractB3SingleContext' | '_extractB3MultiContext'} */
86
+ #b3MethodName
87
+
79
88
  constructor (config) {
80
89
  this._config = config
81
90
 
82
- // v6: `'b3'` is always single-header. v5: env-name decides — OTEL_PROPAGATORS callers expect
83
- // single, the legacy `DD_TRACE_PROPAGATION_STYLE` callers expect multi.
91
+ // v6: `'b3'` is always single-header. v5: `OTEL_PROPAGATORS` callers
92
+ // expect single, legacy `DD_TRACE_PROPAGATION_STYLE` callers expect multi.
93
+ /* istanbul ignore else: v5 fallback, master ships 6.0.0-pre */
84
94
  if (DD_MAJOR >= 6) {
85
- this.#extractB3Context = this._extractB3SingleContext
95
+ this.#b3MethodName = '_extractB3SingleContext'
86
96
  } else {
87
97
  const envName = getConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
88
98
  // eslint-disable-next-line eslint-rules/eslint-env-aliases
89
- this.#extractB3Context = envName === 'OTEL_PROPAGATORS'
90
- ? this._extractB3SingleContext
91
- : this._extractB3MultiContext
99
+ this.#b3MethodName = envName === 'OTEL_PROPAGATORS'
100
+ ? '_extractB3SingleContext'
101
+ : '_extractB3MultiContext'
92
102
  }
93
103
  }
94
104
 
@@ -129,8 +139,7 @@ class TextMapPropagator {
129
139
 
130
140
  extract (carrier) {
131
141
  const spanContext = this._extractSpanContext(carrier)
132
-
133
- if (!spanContext) return spanContext
142
+ if (spanContext === undefined) return null
134
143
 
135
144
  if (extractCh.hasSubscribers) {
136
145
  extractCh.publish({ spanContext, carrier })
@@ -292,7 +301,7 @@ class TextMapPropagator {
292
301
  // v6 keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
293
302
  const hasB3SingleHeader = this._hasPropagationStyle('inject', 'b3 single header') ||
294
303
  (DD_MAJOR >= 6 && this._hasPropagationStyle('inject', 'b3'))
295
- if (!hasB3SingleHeader) return null
304
+ if (!hasB3SingleHeader) return
296
305
 
297
306
  const traceId = this._getB3TraceId(spanContext)
298
307
  const spanId = spanContext._spanId.toString(16)
@@ -361,7 +370,7 @@ class TextMapPropagator {
361
370
  }
362
371
 
363
372
  _hasTraceIdConflict (w3cSpanContext, firstSpanContext) {
364
- return w3cSpanContext !== null &&
373
+ return w3cSpanContext !== undefined &&
365
374
  firstSpanContext.toTraceId(true) === w3cSpanContext.toTraceId(true) &&
366
375
  firstSpanContext.toSpanId() !== w3cSpanContext.toSpanId()
367
376
  }
@@ -372,7 +381,7 @@ class TextMapPropagator {
372
381
 
373
382
  _updateParentIdFromDdHeaders (carrier, firstSpanContext) {
374
383
  const ddCtx = this._extractDatadogContext(carrier)
375
- if (ddCtx !== null) {
384
+ if (ddCtx !== undefined) {
376
385
  firstSpanContext._trace.tags[tags.DD_PARENT_ID] = ddCtx._spanId.toString().padStart(16, '0')
377
386
  }
378
387
  }
@@ -394,35 +403,20 @@ class TextMapPropagator {
394
403
  }
395
404
 
396
405
  _extractSpanContext (carrier) {
397
- let context = null
406
+ let context
398
407
  let style = ''
399
408
  for (const extractor of this._config.tracePropagationStyle.extract) {
400
- let extractedContext = null
401
- switch (extractor) {
402
- case 'datadog':
403
- extractedContext = this._extractDatadogContext(carrier)
404
- break
405
- case 'tracecontext':
406
- extractedContext = this._extractTraceparentContext(carrier)
407
- break
408
- case 'b3 single header':
409
- extractedContext = this._extractB3SingleContext(carrier)
410
- break
411
- case 'b3':
412
- extractedContext = this.#extractB3Context(carrier)
413
- break
414
- case 'b3multi':
415
- extractedContext = this._extractB3MultiContext(carrier)
416
- break
417
- default:
418
- if (extractor !== 'baggage') log.warn('Unknown propagation style:', extractor)
409
+ const method = extractor === 'b3' ? this.#b3MethodName : EXTRACT_STYLE_METHODS.get(extractor)
410
+ if (method === undefined) {
411
+ if (extractor !== 'baggage') log.warn('Unknown propagation style:', extractor)
412
+ continue
419
413
  }
420
-
421
- if (extractedContext === null) { // If the current extractor was invalid, continue to the next extractor
414
+ const extractedContext = this[method](carrier)
415
+ if (extractedContext === undefined) {
422
416
  continue
423
417
  }
424
418
 
425
- if (context === null) {
419
+ if (context === undefined) {
426
420
  context = extractedContext
427
421
  style = extractor
428
422
  if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
@@ -446,9 +440,7 @@ class TextMapPropagator {
446
440
  }
447
441
 
448
442
  if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
449
- // `context` is null when no extractor matched; the fallback below picks up
450
- // the SQSD context if present, otherwise the request runs untraced.
451
- if (context) context._links = []
443
+ if (context !== undefined) context._links = []
452
444
  } else {
453
445
  if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart' && context) {
454
446
  context._links = []
@@ -490,14 +482,17 @@ class TextMapPropagator {
490
482
 
491
483
  _extractB3MultiContext (carrier) {
492
484
  const b3 = this._extractB3MultipleHeaders(carrier)
493
- if (!b3) return null
485
+ if (b3 === undefined) return
494
486
  return this._extractB3Context(b3)
495
487
  }
496
488
 
497
489
  _extractB3SingleContext (carrier) {
498
- if (!b3HeaderExpr.test(carrier[b3HeaderKey])) return null
490
+ // `typeof === 'string'` first; otherwise the regex coerces `undefined` to
491
+ // `'undefined'` and runs on every header-less request.
492
+ const header = carrier[b3HeaderKey]
493
+ if (typeof header !== 'string' || !b3HeaderExpr.test(header)) return
499
494
  const b3 = this._extractB3SingleHeader(carrier)
500
- if (!b3) return null
495
+ if (b3 === undefined) return
501
496
  return this._extractB3Context(b3)
502
497
  }
503
498
 
@@ -527,23 +522,19 @@ class TextMapPropagator {
527
522
 
528
523
  _extractSqsdContext (carrier) {
529
524
  const headerValue = carrier[sqsdHeaderHey]
530
- if (!headerValue) {
531
- return null
532
- }
525
+ if (!headerValue) return
533
526
  let parsed
534
527
  try {
535
528
  parsed = JSON.parse(headerValue)
536
529
  } catch {
537
- return null
530
+ return
538
531
  }
539
532
  return this._extractDatadogContext(parsed)
540
533
  }
541
534
 
542
535
  _extractTraceparentContext (carrier) {
543
536
  const headerValue = carrier[traceparentKey]
544
- if (typeof headerValue !== 'string') {
545
- return null
546
- }
537
+ if (typeof headerValue !== 'string') return
547
538
  const matches = headerValue.trim().match(traceparentExpr)
548
539
  if (matches !== null) {
549
540
  const [, version, traceId, spanId, flags, tail] = matches
@@ -554,14 +545,14 @@ class TextMapPropagator {
554
545
  ? carrier.tracestate.filter(item => typeof item === 'string').join(',')
555
546
  : carrier.tracestate
556
547
  const tracestate = TraceState.fromString(rawTracestate)
557
- if (invalidSegment.test(traceId)) return null
558
- if (invalidSegment.test(spanId)) return null
548
+ if (invalidSegment.test(traceId)) return
549
+ if (invalidSegment.test(spanId)) return
559
550
 
560
551
  // Version ff is considered invalid
561
- if (version === 'ff') return null
552
+ if (version === 'ff') return
562
553
 
563
554
  // Version 00 should have no tail, but future versions may
564
- if (tail && version === '00') return null
555
+ if (tail && version === '00') return
565
556
 
566
557
  const spanContext = new DatadogSpanContext({
567
558
  traceId: id(traceId, 16),
@@ -624,12 +615,11 @@ class TextMapPropagator {
624
615
  this._extractLegacyBaggageItems(carrier, spanContext)
625
616
  return spanContext
626
617
  }
627
- return null
628
618
  }
629
619
 
630
620
  _extractGenericContext (carrier, traceKey, spanKey, radix) {
631
621
  if (carrier && carrier[traceKey] && carrier[spanKey]) {
632
- if (invalidSegment.test(carrier[traceKey])) return null
622
+ if (invalidSegment.test(carrier[traceKey])) return
633
623
 
634
624
  return new DatadogSpanContext({
635
625
  traceId: id(carrier[traceKey], radix),
@@ -637,11 +627,17 @@ class TextMapPropagator {
637
627
  isRemote: true,
638
628
  })
639
629
  }
640
-
641
- return null
642
630
  }
643
631
 
644
632
  _extractB3MultipleHeaders (carrier) {
633
+ // `b3ParentKey` is intentionally absent: this method never consults it,
634
+ // so a parent-id-only carrier should bail with the rest.
635
+ if (carrier[b3TraceKey] === undefined &&
636
+ carrier[b3SampledKey] === undefined &&
637
+ carrier[b3FlagsKey] === undefined) {
638
+ return
639
+ }
640
+
645
641
  let empty = true
646
642
  const b3 = {}
647
643
 
@@ -661,12 +657,12 @@ class TextMapPropagator {
661
657
  empty = false
662
658
  }
663
659
 
664
- return empty ? null : b3
660
+ return empty ? undefined : b3
665
661
  }
666
662
 
667
663
  _extractB3SingleHeader (carrier) {
668
664
  const header = carrier[b3HeaderKey]
669
- if (!header) return null
665
+ if (!header) return
670
666
 
671
667
  const parts = header.split('-')
672
668
 
@@ -705,13 +701,12 @@ class TextMapPropagator {
705
701
  }
706
702
 
707
703
  _extractLegacyBaggageItems (carrier, spanContext) {
708
- if (this._config.legacyBaggageEnabled) {
709
- for (const key of Object.keys(carrier)) {
710
- const match = key.match(baggageExpr)
711
-
712
- if (match) {
713
- spanContext._baggageItems[match[1]] = carrier[key]
714
- }
704
+ if (!this._config.legacyBaggageEnabled) return
705
+ for (const key of Object.keys(carrier)) {
706
+ if (!key.startsWith(baggagePrefix)) continue
707
+ const baggageKey = key.slice(baggagePrefix.length)
708
+ if (baggageKey) {
709
+ spanContext._baggageItems[baggageKey] = carrier[key]
715
710
  }
716
711
  }
717
712
  }