dd-trace 5.99.1 → 5.100.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 (55) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  4. package/packages/datadog-instrumentations/src/express.js +3 -2
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/hono.js +15 -4
  7. package/packages/datadog-instrumentations/src/jest.js +84 -58
  8. package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  11. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  12. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  13. package/packages/datadog-instrumentations/src/router.js +53 -33
  14. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  16. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  17. package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
  18. package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
  20. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  21. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  22. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  23. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  24. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  25. package/packages/datadog-plugin-router/src/index.js +13 -0
  26. package/packages/dd-trace/index.js +4 -3
  27. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  28. package/packages/dd-trace/src/baggage.js +10 -0
  29. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  30. package/packages/dd-trace/src/config/index.js +6 -5
  31. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  32. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  33. package/packages/dd-trace/src/debugger/config.js +1 -1
  34. package/packages/dd-trace/src/encode/0.4.js +1 -1
  35. package/packages/dd-trace/src/encode/tags-processors.js +3 -3
  36. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  37. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  38. package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
  39. package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
  40. package/packages/dd-trace/src/opentelemetry/span.js +14 -42
  41. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  42. package/packages/dd-trace/src/opentracing/propagation/text_map.js +31 -10
  43. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
  44. package/packages/dd-trace/src/opentracing/span.js +3 -2
  45. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  46. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  47. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  48. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  49. package/packages/dd-trace/src/proxy.js +3 -3
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  51. package/packages/dd-trace/src/span_processor.js +1 -1
  52. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  53. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  54. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  55. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -8,11 +8,11 @@ const { threadId } = require('worker_threads')
8
8
  const log = require('./log')
9
9
 
10
10
  async function scheduleSnapshot (config, total) {
11
- if (total > config.heapSnapshot.count) return
11
+ if (total > config.DD_HEAP_SNAPSHOT_COUNT) return
12
12
 
13
- await setTimeout(config.heapSnapshot.interval * 1000, null, { ref: false })
13
+ await setTimeout(config.DD_HEAP_SNAPSHOT_INTERVAL * 1000, null, { ref: false })
14
14
  await clearMemory()
15
- writeHeapSnapshot(getName(config.heapSnapshot.destination))
15
+ writeHeapSnapshot(getName(config.DD_HEAP_SNAPSHOT_DESTINATION))
16
16
  await scheduleSnapshot(config, total + 1)
17
17
  }
18
18
 
@@ -49,7 +49,7 @@ module.exports = {
49
49
  * @param {import('./config/config-base')} config - Tracer configuration
50
50
  */
51
51
  async start (config) {
52
- const destination = config.heapSnapshot.destination
52
+ const destination = config.DD_HEAP_SNAPSHOT_DESTINATION
53
53
 
54
54
  try {
55
55
  await scheduleSnapshot(config, 1)
@@ -30,7 +30,7 @@ const COUNTER_UNIT = '{evaluation}'
30
30
  * If counter creation fails (e.g. the OTel API is not yet available), the call
31
31
  * is silently skipped and retried on the next `finally()` invocation.
32
32
  *
33
- * When `config.otelMetricsEnabled` is false, `finally()` is always a no-op.
33
+ * When `config.DD_METRICS_OTEL_ENABLED` is false, `finally()` is always a no-op.
34
34
  */
35
35
  class EvalMetricsHook {
36
36
  #enabled = false
@@ -40,7 +40,7 @@ class EvalMetricsHook {
40
40
  * @param {import('../config')} config - Tracer configuration object
41
41
  */
42
42
  constructor (config) {
43
- this.#enabled = config.otelMetricsEnabled === true
43
+ this.#enabled = config.DD_METRICS_OTEL_ENABLED === true
44
44
  }
45
45
 
46
46
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
4
4
  const { storage } = require('../../../datadog-core')
5
- const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage')
5
+ const { getAllBaggageItems, setAllBaggageItems, removeAllBaggageItems } = require('../baggage')
6
6
 
7
7
  const SpanContext = require('./span_context')
8
8
 
@@ -64,13 +64,16 @@ class ContextManager {
64
64
  return this._store.run(context, cb, ...args)
65
65
  }
66
66
  const baggages = propagation.getBaggage(context)
67
- let baggageItems = []
68
- if (baggages) {
69
- baggageItems = baggages.getAllEntries()
70
- }
71
- removeAllBaggageItems()
72
- for (const baggage of baggageItems) {
73
- setBaggageItem(baggage[0], baggage[1].value)
67
+ const baggageItems = baggages ? baggages.getAllEntries() : []
68
+ if (baggageItems.length > 0) {
69
+ /** @type {Record<string, string>} */
70
+ const items = {}
71
+ for (const [key, entry] of baggageItems) {
72
+ items[key] = entry.value
73
+ }
74
+ setAllBaggageItems(items)
75
+ } else {
76
+ removeAllBaggageItems()
74
77
  }
75
78
  if (span && span._ddSpan) {
76
79
  const ddSpan = span._ddSpan
@@ -0,0 +1,170 @@
1
+ 'use strict'
2
+
3
+ const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../constants')
4
+ const DatadogSpanContext = require('../opentracing/span_context')
5
+ const TraceState = require('../opentracing/propagation/tracestate')
6
+
7
+ const id = require('../id')
8
+
9
+ /**
10
+ * @typedef {{ toTraceId: (get128?: boolean) => string, toSpanId: (get128?: boolean) => string }} DatadogContextLike
11
+ * @typedef {{ _ddContext: import('../opentracing/span_context') }} OtelBridgeSpanContextLike
12
+ * @typedef {{
13
+ * traceId: string,
14
+ * spanId: string,
15
+ * traceFlags?: number,
16
+ * traceState?: { serialize: () => string }
17
+ * }} OtelSpanContextLike
18
+ * @typedef {DatadogContextLike | OtelBridgeSpanContextLike | OtelSpanContextLike} LinkContextLike
19
+ * @typedef {{ context: LinkContextLike, attributes?: Record<string, unknown> }} OtelLink
20
+ * @typedef {{
21
+ * name?: string,
22
+ * message?: string,
23
+ * stack?: string,
24
+ * type?: string,
25
+ * escaped?: unknown
26
+ * }} ExceptionLike
27
+ * @typedef {{
28
+ * addEvent: (name: string, attributes: Record<string, unknown>, timeInput?: unknown) => unknown
29
+ * }} EventTarget
30
+ */
31
+
32
+ /**
33
+ * Normalize any Datadog/OTel span-context shape to a `DatadogSpanContext`.
34
+ *
35
+ * @param {LinkContextLike | undefined | null} context
36
+ * @returns {import('../opentracing/span_context') | undefined}
37
+ */
38
+ function normalizeLinkContext (context) {
39
+ if (!context) return
40
+
41
+ const bridgeCtx = /** @type {OtelBridgeSpanContextLike} */ (context)
42
+ if (bridgeCtx._ddContext) return bridgeCtx._ddContext
43
+
44
+ const ddCtx = /** @type {DatadogContextLike} */ (context)
45
+ if (typeof ddCtx.toTraceId === 'function' && typeof ddCtx.toSpanId === 'function') {
46
+ return /** @type {import('../opentracing/span_context')} */ (/** @type {unknown} */ (context))
47
+ }
48
+
49
+ const otelCtx = /** @type {OtelSpanContextLike} */ (context)
50
+ if (typeof otelCtx.traceId !== 'string' || typeof otelCtx.spanId !== 'string') return
51
+
52
+ let sampling
53
+ if (typeof otelCtx.traceFlags === 'number') {
54
+ sampling = { priority: otelCtx.traceFlags & 1 }
55
+ }
56
+
57
+ let tracestate
58
+ if (otelCtx.traceState?.serialize) {
59
+ tracestate = TraceState.fromString(otelCtx.traceState.serialize())
60
+ }
61
+
62
+ return new DatadogSpanContext({
63
+ traceId: id(otelCtx.traceId, 16),
64
+ spanId: id(otelCtx.spanId, 16),
65
+ sampling,
66
+ tracestate,
67
+ })
68
+ }
69
+
70
+ /**
71
+ * @param {import('../opentracing/span')} ddSpan
72
+ * @param {string} key
73
+ * @param {unknown} value
74
+ * @returns {void}
75
+ */
76
+ function setOtelAttribute (ddSpan, key, value) {
77
+ if (key === 'http.response.status_code') {
78
+ ddSpan.setTag('http.status_code', String(value))
79
+ }
80
+
81
+ ddSpan.setTag(key, value)
82
+ }
83
+
84
+ /**
85
+ * @param {import('../opentracing/span')} ddSpan
86
+ * @param {Record<string, unknown>} attributes
87
+ * @returns {void}
88
+ */
89
+ function setOtelAttributes (ddSpan, attributes) {
90
+ if ('http.response.status_code' in attributes) {
91
+ attributes['http.status_code'] = String(attributes['http.response.status_code'])
92
+ }
93
+
94
+ ddSpan.addTags(attributes)
95
+ }
96
+
97
+ /**
98
+ * Accepts both `{ context, attributes }` and the deprecated `(context, attrs)` form.
99
+ *
100
+ * @param {import('../opentracing/span')} ddSpan
101
+ * @param {LinkContextLike | OtelLink} link
102
+ * @param {Record<string, unknown>} [attrs]
103
+ * @returns {void}
104
+ */
105
+ function addOtelLink (ddSpan, link, attrs) {
106
+ // TODO: Drop the (context, attrs) form in v6.0.0.
107
+ const linkObj = link && typeof link === 'object' && 'context' in link
108
+ ? /** @type {OtelLink} */ (link)
109
+ : { context: /** @type {LinkContextLike} */ (link), attributes: attrs ?? {} }
110
+
111
+ const ddSpanContext = normalizeLinkContext(linkObj.context)
112
+ if (!ddSpanContext) return
113
+
114
+ ddSpan.addLink({ context: ddSpanContext, attributes: linkObj.attributes })
115
+ }
116
+
117
+ /**
118
+ * @param {import('../opentracing/span')} ddSpan
119
+ * @param {EventTarget} eventTarget
120
+ * @param {ExceptionLike} exception
121
+ * @param {unknown} [timeInput]
122
+ * @returns {void}
123
+ */
124
+ function recordException (ddSpan, eventTarget, exception, timeInput) {
125
+ ddSpan.addTags({
126
+ [ERROR_TYPE]: exception.name,
127
+ [ERROR_MESSAGE]: exception.message,
128
+ [ERROR_STACK]: exception.stack,
129
+ [IGNORE_OTEL_ERROR]: ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true,
130
+ })
131
+
132
+ /** @type {Record<string, unknown>} */
133
+ const attributes = {}
134
+ if (exception.message) attributes['exception.message'] = exception.message
135
+ if (exception.type) attributes['exception.type'] = exception.type
136
+ if (exception.escaped) attributes['exception.escaped'] = exception.escaped
137
+ if (exception.stack) attributes['exception.stacktrace'] = exception.stack
138
+
139
+ eventTarget.addEvent(exception.name ?? 'Error', attributes, timeInput)
140
+ }
141
+
142
+ /**
143
+ * First-call-wins; no-op on ended spans. Only `code === 2` emits Datadog error tags.
144
+ *
145
+ * @param {import('../opentracing/span')} ddSpan
146
+ * @param {{ ended: boolean, _hasStatus: boolean }} bridgeSpan
147
+ * @param {{ code?: number, message?: string }} [status]
148
+ * @returns {void}
149
+ */
150
+ function setStatus (ddSpan, bridgeSpan, { code, message } = {}) {
151
+ if (bridgeSpan.ended || bridgeSpan._hasStatus || !code) return
152
+
153
+ bridgeSpan._hasStatus = true
154
+
155
+ if (code === 2) {
156
+ ddSpan.addTags({
157
+ [ERROR_MESSAGE]: message,
158
+ [IGNORE_OTEL_ERROR]: false,
159
+ })
160
+ }
161
+ }
162
+
163
+ module.exports = {
164
+ addOtelLink,
165
+ normalizeLinkContext,
166
+ recordException,
167
+ setOtelAttribute,
168
+ setOtelAttributes,
169
+ setStatus,
170
+ }
@@ -9,12 +9,18 @@ const { timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/co
9
9
 
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
- const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
13
12
  const { SERVICE_NAME, RESOURCE_NAME, SPAN_KIND } = require('../../../../ext/tags')
14
13
  const kinds = require('../../../../ext/kinds')
15
14
 
16
15
  const id = require('../id')
17
16
  const SpanContext = require('./span_context')
17
+ const {
18
+ addOtelLink,
19
+ recordException,
20
+ setOtelAttribute,
21
+ setOtelAttributes,
22
+ setStatus,
23
+ } = require('./span-helpers')
18
24
 
19
25
  // The one built into OTel rounds so we lose sub-millisecond precision.
20
26
  function hrTimeToMilliseconds (time) {
@@ -195,33 +201,17 @@ class Span {
195
201
  }
196
202
 
197
203
  setAttribute (key, value) {
198
- if (key === 'http.response.status_code') {
199
- this._ddSpan.setTag('http.status_code', value.toString())
200
- }
201
-
202
- this._ddSpan.setTag(key, value)
204
+ setOtelAttribute(this._ddSpan, key, value)
203
205
  return this
204
206
  }
205
207
 
206
208
  setAttributes (attributes) {
207
- if ('http.response.status_code' in attributes) {
208
- attributes['http.status_code'] = attributes['http.response.status_code'].toString()
209
- }
210
-
211
- this._ddSpan.addTags(attributes)
209
+ setOtelAttributes(this._ddSpan, attributes)
212
210
  return this
213
211
  }
214
212
 
215
213
  addLink (link, attrs) {
216
- // TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
217
- if (link instanceof SpanContext) {
218
- link = { context: link, attributes: attrs ?? {} }
219
- }
220
-
221
- const { context, attributes } = link
222
- // Extract dd context
223
- const ddSpanContext = context._ddContext
224
- this._ddSpan.addLink({ context: ddSpanContext, attributes })
214
+ addOtelLink(this._ddSpan, link, attrs)
225
215
  return this
226
216
  }
227
217
 
@@ -244,16 +234,8 @@ class Span {
244
234
  return this.addLink(zeroContext, attributes)
245
235
  }
246
236
 
247
- setStatus ({ code, message }) {
248
- if (!this.ended && !this._hasStatus && code) {
249
- this._hasStatus = true
250
- if (code === 2) {
251
- this._ddSpan.addTags({
252
- [ERROR_MESSAGE]: message,
253
- [IGNORE_OTEL_ERROR]: false,
254
- })
255
- }
256
- }
237
+ setStatus (status) {
238
+ setStatus(this._ddSpan, this, status)
257
239
  return this
258
240
  }
259
241
 
@@ -291,18 +273,8 @@ class Span {
291
273
  }
292
274
 
293
275
  recordException (exception, timeInput) {
294
- this._ddSpan.addTags({
295
- [ERROR_TYPE]: exception.name,
296
- [ERROR_MESSAGE]: exception.message,
297
- [ERROR_STACK]: exception.stack,
298
- [IGNORE_OTEL_ERROR]: this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true,
299
- })
300
- const attributes = {}
301
- if (exception.message) attributes['exception.message'] = exception.message
302
- if (exception.type) attributes['exception.type'] = exception.type
303
- if (exception.escaped) attributes['exception.escaped'] = exception.escaped
304
- if (exception.stack) attributes['exception.stacktrace'] = exception.stack
305
- this.addEvent(exception.name, attributes, timeInput)
276
+ // Route through `this.addEvent` so its time-input conversion applies.
277
+ recordException(this._ddSpan, this, exception, timeInput)
306
278
  }
307
279
 
308
280
  get duration () {
@@ -3,49 +3,16 @@
3
3
  const api = require('@opentelemetry/api')
4
4
  const { sanitizeAttributes } = require('../../../../vendor/dist/@opentelemetry/core')
5
5
 
6
+ const tracer = require('../../')
7
+
6
8
  const id = require('../id')
7
9
  const log = require('../log')
8
- const DatadogSpanContext = require('../opentracing/span_context')
9
10
  const TextMapPropagator = require('../opentracing/propagation/text_map')
10
11
  const TraceState = require('../opentracing/propagation/tracestate')
11
12
  const SpanContext = require('./span_context')
12
13
  const Span = require('./span')
13
14
  const Sampler = require('./sampler')
14
-
15
- function normalizeLinkContext (context) {
16
- if (!context) return
17
-
18
- // OTel API bridge SpanContext wrapper
19
- if (context._ddContext) return context._ddContext
20
-
21
- // Datadog span context
22
- if (typeof context.toTraceId === 'function' && typeof context.toSpanId === 'function') {
23
- return context
24
- }
25
-
26
- // Standard OTel SpanContext (traceId/spanId)
27
- if (typeof context.traceId !== 'string' || typeof context.spanId !== 'string') {
28
- // Invalid
29
- return
30
- }
31
-
32
- let sampling
33
- if (typeof context.traceFlags === 'number') {
34
- sampling = { priority: context.traceFlags & 1 }
35
- }
36
-
37
- let tracestate
38
- if (context.traceState?.serialize) {
39
- tracestate = TraceState.fromString(context.traceState.serialize())
40
- }
41
-
42
- return new DatadogSpanContext({
43
- traceId: id(context.traceId, 16),
44
- spanId: id(context.spanId, 16),
45
- sampling,
46
- tracestate,
47
- })
48
- }
15
+ const { normalizeLinkContext } = require('./span-helpers')
49
16
 
50
17
  class Tracer {
51
18
  constructor (library, config, tracerProvider) {
@@ -140,6 +107,14 @@ class Tracer {
140
107
  spanContext = new SpanContext()
141
108
  }
142
109
 
110
+ // init() didn't finish setting up real tracing (e.g. DD_TRACE_ENABLED=false,
111
+ // or init() was never called), so the inner tracer is still the noop.
112
+ // DatadogSpan can't construct without a processor + prioritySampler, so fall
113
+ // through to a non-recording span; the SpanContext still propagates.
114
+ if (!tracer._tracingInitialized) {
115
+ return api.trace.wrapSpanContext(spanContext)
116
+ }
117
+
143
118
  const spanKind = options.kind || api.SpanKind.INTERNAL
144
119
  const links = []
145
120
  if (options.links?.length) {
@@ -7,7 +7,7 @@ const DatadogSpanContext = require('../span_context')
7
7
  const log = require('../../log')
8
8
  const tags = require('../../../../../ext/tags')
9
9
  const { getConfiguredEnvName } = require('../../config/helper')
10
- const { setBaggageItem, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
10
+ const { setAllBaggageItems, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
11
11
  const telemetryMetrics = require('../../telemetry/metrics')
12
12
 
13
13
  const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
@@ -166,6 +166,7 @@ class TextMapPropagator {
166
166
  }
167
167
  }
168
168
  }
169
+
169
170
  if (this._hasPropagationStyle('inject', 'baggage')) {
170
171
  let baggage = ''
171
172
  let itemCounter = 0
@@ -408,10 +409,10 @@ class TextMapPropagator {
408
409
  }
409
410
  }
410
411
 
411
- if (this._config.tracePropagationBehaviorExtract === 'ignore') {
412
+ if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
412
413
  context._links = []
413
414
  } else {
414
- if (this._config.tracePropagationBehaviorExtract === 'restart') {
415
+ if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart') {
415
416
  context._links = []
416
417
  context._links.push({
417
418
  context,
@@ -675,11 +676,29 @@ class TextMapPropagator {
675
676
  _extractBaggageItems (carrier, spanContext) {
676
677
  removeAllBaggageItems()
677
678
  if (!this._hasPropagationStyle('extract', 'baggage')) return
678
- if (!carrier?.baggage) return
679
- const baggages = carrier.baggage.split(',')
679
+ const baggageHeader = carrier?.baggage
680
+ const header = Array.isArray(baggageHeader) ? baggageHeader.join(',') : baggageHeader
681
+ if (!header) return
682
+
683
+ const baggages = header.split(',')
680
684
  const baggageTagKeys = new Set(this._config.baggageTagKeys)
681
685
  const tagAllKeys = baggageTagKeys.has('*')
686
+ /** @type {Record<string, string> | undefined} */
687
+ let items
688
+ let itemCount = 0
689
+ let byteCount = 0
690
+
682
691
  for (const keyValue of baggages) {
692
+ if (itemCount >= this._config.baggageMaxItems) {
693
+ tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_item_count_exceeded']).inc()
694
+ break
695
+ }
696
+ // Charge the comma slot before the empty-entry skip so a `,,,,,foo=bar` can't iterate for free.
697
+ byteCount += keyValue.length + 1
698
+ if (byteCount > this._config.baggageMaxBytes) {
699
+ tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_byte_count_exceeded']).inc()
700
+ break
701
+ }
683
702
  if (!keyValue) continue
684
703
 
685
704
  // Per W3C baggage, list-members can contain optional properties after `;`.
@@ -692,7 +711,6 @@ class TextMapPropagator {
692
711
  const eqIdx = member.indexOf('=')
693
712
  if (eqIdx === -1) {
694
713
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
695
- removeAllBaggageItems()
696
714
  return
697
715
  }
698
716
 
@@ -701,7 +719,6 @@ class TextMapPropagator {
701
719
 
702
720
  if (!baggageTokenExpr.test(key) || !value) {
703
721
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
704
- removeAllBaggageItems()
705
722
  return
706
723
  }
707
724
  try {
@@ -710,15 +727,19 @@ class TextMapPropagator {
710
727
  const bytes = value.replaceAll(percentByte, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
711
728
  value = Buffer.from(bytes, 'binary').toString('utf8')
712
729
  }
730
+ items ??= {}
731
+ items[key] = value
732
+ itemCount++
713
733
 
714
734
  if (spanContext && (tagAllKeys || baggageTagKeys.has(key))) {
715
735
  spanContext._trace.tags['baggage.' + key] = value
716
736
  }
717
- setBaggageItem(key, value)
718
737
  }
719
738
 
720
- // Successfully extracted baggage
721
- tracerMetrics.count('context_header_style.extracted', ['header_style:baggage']).inc()
739
+ if (items) {
740
+ setAllBaggageItems(items)
741
+ tracerMetrics.count('context_header_style.extracted', ['header_style:baggage']).inc()
742
+ }
722
743
  }
723
744
 
724
745
  _extractSamplingPriority (carrier, spanContext) {
@@ -1,19 +1,49 @@
1
1
  'use strict'
2
2
 
3
- const traceStateRegex = /[ \t]*([^=]+)=([ \t]*[^, \t]+)[ \t]*(,|$)/gim
4
- const traceStateDataRegex = /([^:]+):([^;]+)(;|$)/gim
3
+ // W3C Trace Context §3.3.1.2: max 32 list-members.
4
+ // https://www.w3.org/TR/trace-context/#tracestate-header-field-values
5
+ const MAX_LIST_MEMBERS = 32
6
+ const WHITESPACE = /[ \t]/
5
7
 
6
- function fromString (Type, regex, value) {
7
- if (typeof value !== 'string' || !value.length) {
8
- return new Type()
8
+ /**
9
+ * Parse a separator-delimited string into key/value entries.
10
+ *
11
+ * @param {string} value
12
+ * @param {string} fieldSeparator Between entries.
13
+ * @param {string} pairSeparator Between key and value within an entry.
14
+ * @param {boolean} rejectValueTabs Drop entries whose value contains an internal tab.
15
+ * @returns {[string, string][]} Entries in reverse of wire order.
16
+ */
17
+ function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
18
+ const segments = value.split(fieldSeparator)
19
+ segments.length = Math.min(segments.length, MAX_LIST_MEMBERS)
20
+
21
+ // TODO: We should extract dd no matter at what position and move it to the front of the list.
22
+ // Extract up 31 additional entries.
23
+ const entries = []
24
+ for (let index = 0; index < segments.length; index++) {
25
+ const segment = segments[index]
26
+ const splitIndex = segment.indexOf(pairSeparator)
27
+ if (splitIndex === -1) continue
28
+ const key = segment.slice(0, splitIndex).trim()
29
+ if (!key || WHITESPACE.test(key)) continue
30
+ // W3C §3.3.1.3.2: value = 0*255(chr) nblk-chr; chr = %x20 / nblk-chr (no tab).
31
+ // Leading 0x20 is part of value; trailing whitespace is OWS.
32
+ const entryValue = segment.slice(splitIndex + 1).trimEnd()
33
+ if (!entryValue || rejectValueTabs && entryValue.includes('\t')) continue
34
+ entries.push([key, entryValue])
9
35
  }
36
+ // Reverse so the Map's insertion order is reverse of wire order. `toString`
37
+ // prepends as it iterates, which yields the original wire order back.
38
+ entries.reverse()
39
+ return entries
40
+ }
10
41
 
11
- const values = []
12
- for (const row of value.matchAll(regex)) {
13
- values.unshift(row.slice(1, 3))
42
+ function fromString (Type, value, fieldSeparator, pairSeparator, rejectValueTabs) {
43
+ if (typeof value !== 'string' || !value.length) {
44
+ return new Type()
14
45
  }
15
-
16
- return new Type(values)
46
+ return new Type(parseEntries(value, fieldSeparator, pairSeparator, rejectValueTabs))
17
47
  }
18
48
 
19
49
  function toString (map, pairSeparator, fieldSeparator) {
@@ -52,7 +82,7 @@ class TraceStateData extends Map {
52
82
  }
53
83
 
54
84
  static fromString (value) {
55
- return fromString(TraceStateData, traceStateDataRegex, value)
85
+ return fromString(TraceStateData, value, ';', ':', false)
56
86
  }
57
87
 
58
88
  toString () {
@@ -92,7 +122,7 @@ class TraceState extends Map {
92
122
  }
93
123
 
94
124
  static fromString (value) {
95
- return fromString(TraceState, traceStateRegex, value)
125
+ return fromString(TraceState, value, ',', '=', true)
96
126
  }
97
127
 
98
128
  toString () {
@@ -336,7 +336,8 @@ class DatadogSpan {
336
336
  let startTime
337
337
 
338
338
  let baggage = {}
339
- if (parent && parent._isRemote && this._parentTracer?._config?.tracePropagationBehaviorExtract !== 'continue') {
339
+ const propagationBehavior = this._parentTracer?._config?.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
340
+ if (parent && parent._isRemote && propagationBehavior !== 'continue') {
340
341
  baggage = parent._baggageItems
341
342
  parent = null
342
343
  }
@@ -375,7 +376,7 @@ class DatadogSpan {
375
376
  .padEnd(16, '0')
376
377
  }
377
378
 
378
- if (this._parentTracer?._config?.tracePropagationBehaviorExtract === 'restart') {
379
+ if (propagationBehavior === 'restart') {
379
380
  spanContext._baggageItems = baggage
380
381
  }
381
382
  }