dd-trace 5.99.0 → 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 (74) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/package.json +24 -5
  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 +89 -63
  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-dd-trace-api/src/index.js +1 -1
  22. package/packages/datadog-plugin-graphql/src/utils.js +2 -2
  23. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  24. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  25. package/packages/datadog-plugin-memcached/src/index.js +1 -1
  26. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  27. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  28. package/packages/datadog-plugin-router/src/index.js +13 -0
  29. package/packages/dd-trace/index.js +4 -3
  30. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  31. package/packages/dd-trace/src/appsec/blocking.js +18 -6
  32. package/packages/dd-trace/src/appsec/graphql.js +1 -1
  33. package/packages/dd-trace/src/baggage.js +26 -13
  34. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
  35. package/packages/dd-trace/src/config/generated-config-types.d.ts +45 -69
  36. package/packages/dd-trace/src/config/index.js +13 -12
  37. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  38. package/packages/dd-trace/src/config/supported-configurations.json +31 -76
  39. package/packages/dd-trace/src/debugger/config.js +1 -1
  40. package/packages/dd-trace/src/dogstatsd.js +5 -8
  41. package/packages/dd-trace/src/encode/0.4.js +1 -1
  42. package/packages/dd-trace/src/encode/tags-processors.js +3 -3
  43. package/packages/dd-trace/src/exporter.js +1 -1
  44. package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
  45. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  46. package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
  47. package/packages/dd-trace/src/llmobs/sdk.js +21 -1
  48. package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
  49. package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
  50. package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
  51. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  52. package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
  53. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -5
  54. package/packages/dd-trace/src/opentelemetry/metrics/index.js +6 -6
  55. package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
  56. package/packages/dd-trace/src/opentelemetry/span.js +14 -42
  57. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +1 -1
  58. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  59. package/packages/dd-trace/src/opentracing/propagation/text_map.js +44 -23
  60. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
  61. package/packages/dd-trace/src/opentracing/span.js +4 -3
  62. package/packages/dd-trace/src/plugin_manager.js +6 -6
  63. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  64. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  65. package/packages/dd-trace/src/plugins/util/test.js +295 -29
  66. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  67. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  68. package/packages/dd-trace/src/proxy.js +9 -9
  69. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  70. package/packages/dd-trace/src/span_processor.js +1 -1
  71. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  72. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  73. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  74. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -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
@@ -60,18 +60,18 @@ function initializeOpenTelemetryLogs (config) {
60
60
 
61
61
  // Create OTLP exporter using resolved config values
62
62
  const exporter = new OtlpHttpLogExporter(
63
- config.otelLogsUrl,
63
+ config.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
64
64
  config.OTEL_EXPORTER_OTLP_LOGS_HEADERS,
65
- config.otelLogsTimeout,
66
- config.otelLogsProtocol,
65
+ config.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
66
+ config.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
67
67
  resourceAttributes
68
68
  )
69
69
 
70
70
  // Create batch processor for exporting logs to Datadog Agent
71
71
  const processor = new BatchLogRecordProcessor(
72
72
  exporter,
73
- config.otelBatchTimeout,
74
- config.otelMaxExportBatchSize
73
+ config.OTEL_BSP_SCHEDULE_DELAY,
74
+ config.OTEL_BSP_MAX_EXPORT_BATCH_SIZE
75
75
  )
76
76
 
77
77
  // Create logger provider with processor for Datadog Agent export
@@ -57,18 +57,18 @@ function initializeOpenTelemetryMetrics (config) {
57
57
  }
58
58
 
59
59
  const exporter = new OtlpHttpMetricExporter(
60
- config.otelMetricsUrl,
60
+ config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
61
61
  config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
62
- config.otelMetricsTimeout,
63
- config.otelMetricsProtocol,
62
+ config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
63
+ config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
64
64
  resourceAttributes
65
65
  )
66
66
 
67
67
  const reader = new PeriodicMetricReader(
68
68
  exporter,
69
- config.otelMetricsExportInterval,
70
- config.otelMetricsTemporalityPreference,
71
- config.otelMaxQueueSize
69
+ config.OTEL_METRIC_EXPORT_INTERVAL,
70
+ config.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
71
+ config.OTEL_BSP_MAX_QUEUE_SIZE
72
72
  )
73
73
 
74
74
  const meterProvider = new MeterProvider({ reader })
@@ -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 () {
@@ -19,7 +19,7 @@ const OtlpTraceTransformer = require('./otlp_transformer')
19
19
  * (https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor).
20
20
  * Currently each finished trace is sent as its own HTTP request, which is
21
21
  * unsuitable for high-traffic production environments. The config values
22
- * `otelBatchTimeout`, `otelMaxExportBatchSize`, and `otelMaxQueueSize`
22
+ * `OTEL_BSP_SCHEDULE_DELAY`, `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`, and `OTEL_BSP_MAX_QUEUE_SIZE`
23
23
  * (OTEL_BSP_*) are already defined and should drive that implementation.
24
24
  *
25
25
  * @class OtlpHttpTraceExporter
@@ -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')
@@ -64,6 +64,7 @@ const tracestateTagValueFilter = /[^\x20-\x2B\x2D-\x3A\x3C-\x7D]/g
64
64
  const invalidSegment = /^0+$/
65
65
  const zeroTraceId = '0000000000000000'
66
66
  const hex16 = /^[0-9A-Fa-f]{16}$/
67
+ const percentByte = /%([0-9A-Fa-f]{2})/g
67
68
 
68
69
  class TextMapPropagator {
69
70
  #extractB3Context
@@ -165,6 +166,7 @@ class TextMapPropagator {
165
166
  }
166
167
  }
167
168
  }
169
+
168
170
  if (this._hasPropagationStyle('inject', 'baggage')) {
169
171
  let baggage = ''
170
172
  let itemCounter = 0
@@ -173,15 +175,15 @@ class TextMapPropagator {
173
175
  const baggageItems = getAllBaggageItems()
174
176
  if (!baggageItems) return
175
177
  for (const [key, value] of Object.entries(baggageItems)) {
176
- const baggageKey = String(key).trim()
177
- if (!baggageKey || !baggageTokenExpr.test(baggageKey)) continue
178
+ const baggageKey = key.trim()
179
+ if (!baggageTokenExpr.test(baggageKey)) continue
178
180
 
179
181
  // Do not trim values. If callers include leading/trailing whitespace, it must be percent-encoded.
180
182
  // W3C list-member allows optional properties after ';'.
181
183
  // https://www.w3.org/TR/baggage/#header-content
182
- const item = `${baggageKey}=${encodeURIComponent(String(value))},`
184
+ const item = `${baggageKey}=${encodeURIComponent(value)},`
183
185
  itemCounter += 1
184
- byteCounter += Buffer.byteLength(item)
186
+ byteCounter += item.length
185
187
 
186
188
  // Check for item count limit exceeded
187
189
  if (itemCounter > this._config.baggageMaxItems) {
@@ -209,7 +211,7 @@ class TextMapPropagator {
209
211
  _injectTags (spanContext, carrier) {
210
212
  const trace = spanContext._trace
211
213
 
212
- if (this._config.tagsHeaderMaxLength === 0) {
214
+ if (this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
213
215
  log.debug('Trace tag propagation is disabled, skipping injection.')
214
216
  return
215
217
  }
@@ -228,7 +230,7 @@ class TextMapPropagator {
228
230
 
229
231
  const header = tags.join(',')
230
232
 
231
- if (header.length > this._config.tagsHeaderMaxLength) {
233
+ if (header.length > this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
232
234
  log.error('Trace tags from span are too large, skipping injection.')
233
235
  } else if (header) {
234
236
  carrier[tagsKey] = header
@@ -387,7 +389,7 @@ class TextMapPropagator {
387
389
  if (context === null) {
388
390
  context = extractedContext
389
391
  style = extractor
390
- if (this._config.tracePropagationExtractFirst) {
392
+ if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) {
391
393
  break
392
394
  }
393
395
  } else {
@@ -407,10 +409,10 @@ class TextMapPropagator {
407
409
  }
408
410
  }
409
411
 
410
- if (this._config.tracePropagationBehaviorExtract === 'ignore') {
412
+ if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
411
413
  context._links = []
412
414
  } else {
413
- if (this._config.tracePropagationBehaviorExtract === 'restart') {
415
+ if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart') {
414
416
  context._links = []
415
417
  context._links.push({
416
418
  context,
@@ -436,7 +438,7 @@ class TextMapPropagator {
436
438
  this._extractSamplingPriority(carrier, spanContext)
437
439
  this._extractTags(carrier, spanContext)
438
440
 
439
- if (this._config.tracePropagationExtractFirst) return spanContext
441
+ if (this._config.DD_TRACE_PROPAGATION_EXTRACT_FIRST) return spanContext
440
442
 
441
443
  const tc = this._extractTraceparentContext(carrier)
442
444
 
@@ -674,11 +676,29 @@ class TextMapPropagator {
674
676
  _extractBaggageItems (carrier, spanContext) {
675
677
  removeAllBaggageItems()
676
678
  if (!this._hasPropagationStyle('extract', 'baggage')) return
677
- if (!carrier?.baggage) return
678
- 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(',')
679
684
  const baggageTagKeys = new Set(this._config.baggageTagKeys)
680
685
  const tagAllKeys = baggageTagKeys.has('*')
686
+ /** @type {Record<string, string> | undefined} */
687
+ let items
688
+ let itemCount = 0
689
+ let byteCount = 0
690
+
681
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
+ }
682
702
  if (!keyValue) continue
683
703
 
684
704
  // Per W3C baggage, list-members can contain optional properties after `;`.
@@ -691,7 +711,6 @@ class TextMapPropagator {
691
711
  const eqIdx = member.indexOf('=')
692
712
  if (eqIdx === -1) {
693
713
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
694
- removeAllBaggageItems()
695
714
  return
696
715
  }
697
716
 
@@ -700,25 +719,27 @@ class TextMapPropagator {
700
719
 
701
720
  if (!baggageTokenExpr.test(key) || !value) {
702
721
  tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
703
- removeAllBaggageItems()
704
722
  return
705
723
  }
706
724
  try {
707
725
  value = decodeURIComponent(value)
708
726
  } catch {
709
- tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
710
- removeAllBaggageItems()
711
- return
727
+ const bytes = value.replaceAll(percentByte, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
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) {
@@ -734,9 +755,9 @@ class TextMapPropagator {
734
755
 
735
756
  const trace = spanContext._trace
736
757
 
737
- if (this._config.tagsHeaderMaxLength === 0) {
758
+ if (this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
738
759
  log.debug('Trace tag propagation is disabled, skipping extraction.')
739
- } else if (carrier[tagsKey].length > this._config.tagsHeaderMaxLength) {
760
+ } else if (carrier[tagsKey].length > this._config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
740
761
  log.error('Trace tags from carrier are too large, skipping extraction.')
741
762
  } else {
742
763
  const pairs = carrier[tagsKey].split(',')