dd-trace 5.99.1 → 5.101.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.
- package/LICENSE-3rdparty.csv +0 -1
- package/index.d.ts +14 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +146 -90
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
- package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/reporter.js +4 -1
- package/packages/dd-trace/src/baggage.js +10 -0
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +7 -60
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +17 -15
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +6 -3
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +3 -3
- package/packages/dd-trace/src/encode/0.5.js +2 -2
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +5 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +80 -5
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
- package/packages/dd-trace/src/opentelemetry/span.js +42 -108
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
- package/packages/dd-trace/src/opentracing/span.js +58 -49
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -3
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +1 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/packages/dd-trace/src/util.js +17 -0
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -9,31 +9,13 @@ 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')
|
|
16
|
+
const BridgeSpanBase = require('./bridge-span-base')
|
|
17
17
|
const SpanContext = require('./span_context')
|
|
18
|
-
|
|
19
|
-
// The one built into OTel rounds so we lose sub-millisecond precision.
|
|
20
|
-
function hrTimeToMilliseconds (time) {
|
|
21
|
-
return time[0] * 1e3 + time[1] / 1e6
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isTimeInput (startTime) {
|
|
25
|
-
if (typeof startTime === 'number') {
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
28
|
-
if (startTime instanceof Date) {
|
|
29
|
-
return true
|
|
30
|
-
}
|
|
31
|
-
if (Array.isArray(startTime) && startTime.length === 2 &&
|
|
32
|
-
typeof startTime[0] === 'number' && typeof startTime[1] === 'number') {
|
|
33
|
-
return true
|
|
34
|
-
}
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
18
|
+
const { setOtelOperationName } = require('./span-helpers')
|
|
37
19
|
|
|
38
20
|
const spanKindNames = {
|
|
39
21
|
[api.SpanKind.INTERNAL]: kinds.INTERNAL,
|
|
@@ -43,6 +25,15 @@ const spanKindNames = {
|
|
|
43
25
|
[api.SpanKind.CONSUMER]: kinds.CONSUMER,
|
|
44
26
|
}
|
|
45
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The OTel-shipped `hrTimeToMilliseconds` rounds, dropping sub-millisecond precision we want.
|
|
30
|
+
*
|
|
31
|
+
* @param {[number, number]} hrTime
|
|
32
|
+
*/
|
|
33
|
+
function hrTimeToMilliseconds (hrTime) {
|
|
34
|
+
return hrTime[0] * 1e3 + hrTime[1] / 1e6
|
|
35
|
+
}
|
|
36
|
+
|
|
46
37
|
/**
|
|
47
38
|
* Several of these attributes are not yet supported by the Node.js OTel API.
|
|
48
39
|
* We check for old equivalents where we can, but not all had equivalents.
|
|
@@ -122,7 +113,21 @@ function spanNameMapper (spanName, kind, attributes) {
|
|
|
122
113
|
return spanKindNames[kind]
|
|
123
114
|
}
|
|
124
115
|
|
|
125
|
-
|
|
116
|
+
/**
|
|
117
|
+
* OTel-bridge span backed by a `DatadogSpan`. `Tracer` constructs these on the OTel API
|
|
118
|
+
* surface; the underlying DD span carries the lifecycle.
|
|
119
|
+
*/
|
|
120
|
+
class Span extends BridgeSpanBase {
|
|
121
|
+
/**
|
|
122
|
+
* @param {import('./tracer')} parentTracer
|
|
123
|
+
* @param {import('@opentelemetry/api').Context} context
|
|
124
|
+
* @param {string | undefined} spanName
|
|
125
|
+
* @param {import('./span_context')} spanContext
|
|
126
|
+
* @param {import('@opentelemetry/api').SpanKind} kind
|
|
127
|
+
* @param {Array<import('@opentelemetry/api').Link>} [links]
|
|
128
|
+
* @param {import('@opentelemetry/api').TimeInput} [timeInput]
|
|
129
|
+
* @param {import('@opentelemetry/api').Attributes} [attributes]
|
|
130
|
+
*/
|
|
126
131
|
constructor (
|
|
127
132
|
parentTracer,
|
|
128
133
|
context,
|
|
@@ -138,7 +143,7 @@ class Span {
|
|
|
138
143
|
const hrStartTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
|
|
139
144
|
const startTime = hrTimeToMilliseconds(hrStartTime)
|
|
140
145
|
|
|
141
|
-
|
|
146
|
+
const ddSpan = new DatadogSpan(_tracer, _tracer._processor, _tracer._prioritySampler, {
|
|
142
147
|
operationName: spanNameMapper(spanName, kind, attributes),
|
|
143
148
|
context: spanContext._ddContext,
|
|
144
149
|
startTime,
|
|
@@ -152,6 +157,8 @@ class Span {
|
|
|
152
157
|
links,
|
|
153
158
|
}, _tracer._debug)
|
|
154
159
|
|
|
160
|
+
super(ddSpan)
|
|
161
|
+
|
|
155
162
|
if (attributes) {
|
|
156
163
|
this.setAttributes(attributes)
|
|
157
164
|
}
|
|
@@ -159,8 +166,6 @@ class Span {
|
|
|
159
166
|
this._parentTracer = parentTracer
|
|
160
167
|
this._context = context
|
|
161
168
|
|
|
162
|
-
this._hasStatus = false
|
|
163
|
-
|
|
164
169
|
// NOTE: Need to grab the value before setting it on the span because the
|
|
165
170
|
// math for computing opentracing timestamps is apparently lossy...
|
|
166
171
|
this.startTime = hrStartTime
|
|
@@ -194,43 +199,13 @@ class Span {
|
|
|
194
199
|
return new SpanContext(this._ddSpan.context())
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
this._ddSpan.setTag(key, value)
|
|
203
|
-
return this
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
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)
|
|
212
|
-
return this
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
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 })
|
|
225
|
-
return this
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
addLinks (links) {
|
|
229
|
-
for (const link of links) this.addLink(link)
|
|
230
|
-
return this
|
|
231
|
-
}
|
|
232
|
-
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} ptrKind
|
|
204
|
+
* @param {string} ptrDir
|
|
205
|
+
* @param {string} ptrHash
|
|
206
|
+
*/
|
|
233
207
|
addSpanPointer (ptrKind, ptrDir, ptrHash) {
|
|
208
|
+
if (this.ended) return this
|
|
234
209
|
const zeroContext = new SpanContext({
|
|
235
210
|
traceId: id('0'),
|
|
236
211
|
spanId: id('0'),
|
|
@@ -244,26 +219,17 @@ class Span {
|
|
|
244
219
|
return this.addLink(zeroContext, attributes)
|
|
245
220
|
}
|
|
246
221
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (code === 2) {
|
|
251
|
-
this._ddSpan.addTags({
|
|
252
|
-
[ERROR_MESSAGE]: message,
|
|
253
|
-
[IGNORE_OTEL_ERROR]: false,
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return this
|
|
258
|
-
}
|
|
259
|
-
|
|
222
|
+
/**
|
|
223
|
+
* @param {string} name
|
|
224
|
+
*/
|
|
260
225
|
updateName (name) {
|
|
261
|
-
|
|
262
|
-
this._ddSpan.setOperationName(name)
|
|
263
|
-
}
|
|
226
|
+
setOtelOperationName(this._ddSpan, name)
|
|
264
227
|
return this
|
|
265
228
|
}
|
|
266
229
|
|
|
230
|
+
/**
|
|
231
|
+
* @param {import('@opentelemetry/api').TimeInput} [timeInput]
|
|
232
|
+
*/
|
|
267
233
|
end (timeInput) {
|
|
268
234
|
if (this.ended) {
|
|
269
235
|
api.diag.error('You can only call end() on a span once.')
|
|
@@ -277,41 +243,9 @@ class Span {
|
|
|
277
243
|
this._spanProcessor.onEnd(this)
|
|
278
244
|
}
|
|
279
245
|
|
|
280
|
-
isRecording () {
|
|
281
|
-
return this.ended === false
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
addEvent (name, attributesOrStartTime, startTime) {
|
|
285
|
-
startTime = attributesOrStartTime && isTimeInput(attributesOrStartTime) ? attributesOrStartTime : startTime
|
|
286
|
-
const hrStartTime = timeInputToHrTime(startTime || (performance.now() + timeOrigin))
|
|
287
|
-
startTime = hrTimeToMilliseconds(hrStartTime)
|
|
288
|
-
|
|
289
|
-
this._ddSpan.addEvent(name, attributesOrStartTime, startTime)
|
|
290
|
-
return this
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
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)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
246
|
get duration () {
|
|
309
247
|
return this._ddSpan._duration
|
|
310
248
|
}
|
|
311
|
-
|
|
312
|
-
get ended () {
|
|
313
|
-
return this.duration !== undefined
|
|
314
|
-
}
|
|
315
249
|
}
|
|
316
250
|
|
|
317
251
|
module.exports = Span
|
|
@@ -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 {
|
|
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')
|
|
@@ -69,6 +69,12 @@ const percentByte = /%([0-9A-Fa-f]{2})/g
|
|
|
69
69
|
class TextMapPropagator {
|
|
70
70
|
#extractB3Context
|
|
71
71
|
|
|
72
|
+
/** @type {Set<string> | undefined} Cached `Set` view of `_config.baggageTagKeys`. */
|
|
73
|
+
#baggageTagKeysSet
|
|
74
|
+
|
|
75
|
+
/** @type {string[] | undefined} Source array that `#baggageTagKeysSet` was built from. */
|
|
76
|
+
#baggageTagKeysSetSource
|
|
77
|
+
|
|
72
78
|
constructor (config) {
|
|
73
79
|
this._config = config
|
|
74
80
|
|
|
@@ -78,6 +84,23 @@ class TextMapPropagator {
|
|
|
78
84
|
this.#extractB3Context = envName === 'OTEL_PROPAGATORS' ? this._extractB3SingleContext : this._extractB3MultiContext
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Returns a `Set` view of `_config.baggageTagKeys` that is rebuilt only
|
|
89
|
+
* when the source array reference changes. Avoids an `O(n)` `Set` alloc
|
|
90
|
+
* per baggage extract (which is per-request when baggage propagation is
|
|
91
|
+
* enabled).
|
|
92
|
+
*
|
|
93
|
+
* @returns {Set<string>}
|
|
94
|
+
*/
|
|
95
|
+
#getBaggageTagKeysSet () {
|
|
96
|
+
const source = this._config.baggageTagKeys
|
|
97
|
+
if (this.#baggageTagKeysSetSource !== source) {
|
|
98
|
+
this.#baggageTagKeysSet = new Set(source)
|
|
99
|
+
this.#baggageTagKeysSetSource = source
|
|
100
|
+
}
|
|
101
|
+
return this.#baggageTagKeysSet
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
inject (spanContext, carrier) {
|
|
82
105
|
if (!carrier) return
|
|
83
106
|
this._injectBaggageItems(spanContext, carrier)
|
|
@@ -166,6 +189,7 @@ class TextMapPropagator {
|
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
191
|
}
|
|
192
|
+
|
|
169
193
|
if (this._hasPropagationStyle('inject', 'baggage')) {
|
|
170
194
|
let baggage = ''
|
|
171
195
|
let itemCounter = 0
|
|
@@ -173,14 +197,14 @@ class TextMapPropagator {
|
|
|
173
197
|
|
|
174
198
|
const baggageItems = getAllBaggageItems()
|
|
175
199
|
if (!baggageItems) return
|
|
176
|
-
for (const
|
|
200
|
+
for (const key of Object.keys(baggageItems)) {
|
|
177
201
|
const baggageKey = key.trim()
|
|
178
202
|
if (!baggageTokenExpr.test(baggageKey)) continue
|
|
179
203
|
|
|
180
204
|
// Do not trim values. If callers include leading/trailing whitespace, it must be percent-encoded.
|
|
181
205
|
// W3C list-member allows optional properties after ';'.
|
|
182
206
|
// https://www.w3.org/TR/baggage/#header-content
|
|
183
|
-
const item = `${baggageKey}=${encodeURIComponent(
|
|
207
|
+
const item = `${baggageKey}=${encodeURIComponent(baggageItems[key])},`
|
|
184
208
|
itemCounter += 1
|
|
185
209
|
byteCounter += item.length
|
|
186
210
|
|
|
@@ -217,14 +241,15 @@ class TextMapPropagator {
|
|
|
217
241
|
|
|
218
242
|
const tags = []
|
|
219
243
|
|
|
220
|
-
for (const key
|
|
221
|
-
|
|
222
|
-
if (!
|
|
244
|
+
for (const key of Object.keys(trace.tags)) {
|
|
245
|
+
const value = trace.tags[key]
|
|
246
|
+
if (!value || !key.startsWith('_dd.p.')) continue
|
|
247
|
+
if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
|
|
223
248
|
log.error('Trace tags from span are invalid, skipping injection.')
|
|
224
249
|
return
|
|
225
250
|
}
|
|
226
251
|
|
|
227
|
-
tags.push(`${key}=${
|
|
252
|
+
tags.push(`${key}=${value}`)
|
|
228
253
|
}
|
|
229
254
|
|
|
230
255
|
const header = tags.join(',')
|
|
@@ -274,7 +299,7 @@ class TextMapPropagator {
|
|
|
274
299
|
const {
|
|
275
300
|
_sampling: { priority, mechanism },
|
|
276
301
|
_tracestate: ts = new TraceState(),
|
|
277
|
-
_trace: { origin, tags },
|
|
302
|
+
_trace: { origin, tags: traceTags },
|
|
278
303
|
} = spanContext
|
|
279
304
|
|
|
280
305
|
carrier[traceparentKey] = spanContext.toTraceparent()
|
|
@@ -296,21 +321,22 @@ class TextMapPropagator {
|
|
|
296
321
|
if (typeof origin === 'string') {
|
|
297
322
|
const originValue = origin
|
|
298
323
|
.replaceAll(tracestateOriginFilter, '_')
|
|
299
|
-
.replaceAll(
|
|
324
|
+
.replaceAll('=', '~')
|
|
300
325
|
|
|
301
326
|
state.set('o', originValue)
|
|
302
327
|
}
|
|
303
328
|
|
|
304
|
-
for (const key
|
|
305
|
-
|
|
329
|
+
for (const key of Object.keys(traceTags)) {
|
|
330
|
+
const tagValueRaw = traceTags[key]
|
|
331
|
+
if (!tagValueRaw || !key.startsWith('_dd.p.')) continue
|
|
306
332
|
|
|
307
333
|
const tagKey = 't.' + key.slice(6)
|
|
308
334
|
.replaceAll(tracestateTagKeyFilter, '_')
|
|
309
335
|
|
|
310
|
-
const tagValue =
|
|
336
|
+
const tagValue = tagValueRaw
|
|
311
337
|
.toString()
|
|
312
338
|
.replaceAll(tracestateTagValueFilter, '_')
|
|
313
|
-
.replaceAll(
|
|
339
|
+
.replaceAll('=', '~')
|
|
314
340
|
|
|
315
341
|
state.set(tagKey, tagValue)
|
|
316
342
|
}
|
|
@@ -408,10 +434,12 @@ class TextMapPropagator {
|
|
|
408
434
|
}
|
|
409
435
|
}
|
|
410
436
|
|
|
411
|
-
if (this._config.
|
|
412
|
-
context
|
|
437
|
+
if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
|
|
438
|
+
// `context` is null when no extractor matched; the fallback below picks up
|
|
439
|
+
// the SQSD context if present, otherwise the request runs untraced.
|
|
440
|
+
if (context) context._links = []
|
|
413
441
|
} else {
|
|
414
|
-
if (this._config.
|
|
442
|
+
if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart' && context) {
|
|
415
443
|
context._links = []
|
|
416
444
|
context._links.push({
|
|
417
445
|
context,
|
|
@@ -502,14 +530,19 @@ class TextMapPropagator {
|
|
|
502
530
|
|
|
503
531
|
_extractTraceparentContext (carrier) {
|
|
504
532
|
const headerValue = carrier[traceparentKey]
|
|
505
|
-
if (
|
|
533
|
+
if (typeof headerValue !== 'string') {
|
|
506
534
|
return null
|
|
507
535
|
}
|
|
508
536
|
const matches = headerValue.trim().match(traceparentExpr)
|
|
509
|
-
if (matches
|
|
510
|
-
const [version, traceId, spanId, flags, tail] = matches
|
|
537
|
+
if (matches !== null) {
|
|
538
|
+
const [, version, traceId, spanId, flags, tail] = matches
|
|
511
539
|
const traceparent = { version }
|
|
512
|
-
|
|
540
|
+
// W3C Trace Context §3.3.1.1: multiple tracestate fields MUST be combined per RFC 7230 §3.2.2.
|
|
541
|
+
// `filter` drops non-string members (Symbol, throwing-toString) that would crash `join`.
|
|
542
|
+
const rawTracestate = Array.isArray(carrier.tracestate)
|
|
543
|
+
? carrier.tracestate.filter(item => typeof item === 'string').join(',')
|
|
544
|
+
: carrier.tracestate
|
|
545
|
+
const tracestate = TraceState.fromString(rawTracestate)
|
|
513
546
|
if (invalidSegment.test(traceId)) return null
|
|
514
547
|
if (invalidSegment.test(spanId)) return null
|
|
515
548
|
|
|
@@ -523,7 +556,7 @@ class TextMapPropagator {
|
|
|
523
556
|
traceId: id(traceId, 16),
|
|
524
557
|
spanId: id(spanId, 16),
|
|
525
558
|
isRemote: true,
|
|
526
|
-
sampling: { priority: Number.parseInt(flags,
|
|
559
|
+
sampling: { priority: Number.parseInt(flags, 16) & 1 ? 1 : 0 },
|
|
527
560
|
traceparent,
|
|
528
561
|
tracestate,
|
|
529
562
|
})
|
|
@@ -549,7 +582,7 @@ class TextMapPropagator {
|
|
|
549
582
|
break
|
|
550
583
|
}
|
|
551
584
|
case 'o':
|
|
552
|
-
spanContext._trace.origin = value
|
|
585
|
+
spanContext._trace.origin = value.replaceAll('~', '=')
|
|
553
586
|
break
|
|
554
587
|
case 't.dm': {
|
|
555
588
|
const mechanism = Math.abs(Number.parseInt(value, 10))
|
|
@@ -562,7 +595,7 @@ class TextMapPropagator {
|
|
|
562
595
|
default: {
|
|
563
596
|
if (!key.startsWith('t.')) continue
|
|
564
597
|
const subKey = key.slice(2) // e.g. t.tid -> tid
|
|
565
|
-
const transformedValue = value.replaceAll(
|
|
598
|
+
const transformedValue = value.replaceAll('~', '=')
|
|
566
599
|
|
|
567
600
|
// If subkey is tid then do nothing because trace header tid should always be preserved
|
|
568
601
|
if (subKey === 'tid') {
|
|
@@ -675,11 +708,29 @@ class TextMapPropagator {
|
|
|
675
708
|
_extractBaggageItems (carrier, spanContext) {
|
|
676
709
|
removeAllBaggageItems()
|
|
677
710
|
if (!this._hasPropagationStyle('extract', 'baggage')) return
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
|
|
711
|
+
const baggageHeader = carrier?.baggage
|
|
712
|
+
const header = Array.isArray(baggageHeader) ? baggageHeader.join(',') : baggageHeader
|
|
713
|
+
if (!header) return
|
|
714
|
+
|
|
715
|
+
const baggages = header.split(',')
|
|
716
|
+
const baggageTagKeys = this.#getBaggageTagKeysSet()
|
|
681
717
|
const tagAllKeys = baggageTagKeys.has('*')
|
|
718
|
+
/** @type {Record<string, string> | undefined} */
|
|
719
|
+
let items
|
|
720
|
+
let itemCount = 0
|
|
721
|
+
let byteCount = 0
|
|
722
|
+
|
|
682
723
|
for (const keyValue of baggages) {
|
|
724
|
+
if (itemCount >= this._config.baggageMaxItems) {
|
|
725
|
+
tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_item_count_exceeded']).inc()
|
|
726
|
+
break
|
|
727
|
+
}
|
|
728
|
+
// Charge the comma slot before the empty-entry skip so a `,,,,,foo=bar` can't iterate for free.
|
|
729
|
+
byteCount += keyValue.length + 1
|
|
730
|
+
if (byteCount > this._config.baggageMaxBytes) {
|
|
731
|
+
tracerMetrics.count('context_header.truncated', ['truncation_reason:baggage_byte_count_exceeded']).inc()
|
|
732
|
+
break
|
|
733
|
+
}
|
|
683
734
|
if (!keyValue) continue
|
|
684
735
|
|
|
685
736
|
// Per W3C baggage, list-members can contain optional properties after `;`.
|
|
@@ -692,7 +743,6 @@ class TextMapPropagator {
|
|
|
692
743
|
const eqIdx = member.indexOf('=')
|
|
693
744
|
if (eqIdx === -1) {
|
|
694
745
|
tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
|
|
695
|
-
removeAllBaggageItems()
|
|
696
746
|
return
|
|
697
747
|
}
|
|
698
748
|
|
|
@@ -701,24 +751,33 @@ class TextMapPropagator {
|
|
|
701
751
|
|
|
702
752
|
if (!baggageTokenExpr.test(key) || !value) {
|
|
703
753
|
tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
|
|
704
|
-
removeAllBaggageItems()
|
|
705
754
|
return
|
|
706
755
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
756
|
+
// `decodeURIComponent` only does work when the value contains a
|
|
757
|
+
// percent-encoded sequence; everything else passes through unchanged.
|
|
758
|
+
// Skipping the call (and the surrounding `try` frame) shaves an alloc
|
|
759
|
+
// per baggage entry on the dominant ASCII case.
|
|
760
|
+
if (value.includes('%')) {
|
|
761
|
+
try {
|
|
762
|
+
value = decodeURIComponent(value)
|
|
763
|
+
} catch {
|
|
764
|
+
const bytes = value.replaceAll(percentByte, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
|
|
765
|
+
value = Buffer.from(bytes, 'binary').toString('utf8')
|
|
766
|
+
}
|
|
712
767
|
}
|
|
768
|
+
items ??= {}
|
|
769
|
+
items[key] = value
|
|
770
|
+
itemCount++
|
|
713
771
|
|
|
714
772
|
if (spanContext && (tagAllKeys || baggageTagKeys.has(key))) {
|
|
715
773
|
spanContext._trace.tags['baggage.' + key] = value
|
|
716
774
|
}
|
|
717
|
-
setBaggageItem(key, value)
|
|
718
775
|
}
|
|
719
776
|
|
|
720
|
-
|
|
721
|
-
|
|
777
|
+
if (items) {
|
|
778
|
+
setAllBaggageItems(items)
|
|
779
|
+
tracerMetrics.count('context_header_style.extracted', ['header_style:baggage']).inc()
|
|
780
|
+
}
|
|
722
781
|
}
|
|
723
782
|
|
|
724
783
|
_extractSamplingPriority (carrier, spanContext) {
|