dd-trace 5.100.0 → 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/index.d.ts +14 -0
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +62 -32
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -4
- package/packages/datadog-instrumentations/src/mocha/worker.js +5 -2
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-plugin-bullmq/src/consumer.js +2 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +14 -20
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/dd-trace/src/appsec/reporter.js +4 -1
- 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/index.js +1 -55
- 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 +5 -2
- 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 +2 -2
- 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/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/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 +11 -2
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +188 -50
- package/packages/dd-trace/src/opentelemetry/span.js +42 -80
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +65 -27
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +58 -22
- package/packages/dd-trace/src/opentracing/span.js +56 -48
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- 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/remote_config/index.js +5 -3
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +0 -4
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/util.js +17 -0
|
@@ -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)
|
|
@@ -174,14 +197,14 @@ class TextMapPropagator {
|
|
|
174
197
|
|
|
175
198
|
const baggageItems = getAllBaggageItems()
|
|
176
199
|
if (!baggageItems) return
|
|
177
|
-
for (const
|
|
200
|
+
for (const key of Object.keys(baggageItems)) {
|
|
178
201
|
const baggageKey = key.trim()
|
|
179
202
|
if (!baggageTokenExpr.test(baggageKey)) continue
|
|
180
203
|
|
|
181
204
|
// Do not trim values. If callers include leading/trailing whitespace, it must be percent-encoded.
|
|
182
205
|
// W3C list-member allows optional properties after ';'.
|
|
183
206
|
// https://www.w3.org/TR/baggage/#header-content
|
|
184
|
-
const item = `${baggageKey}=${encodeURIComponent(
|
|
207
|
+
const item = `${baggageKey}=${encodeURIComponent(baggageItems[key])},`
|
|
185
208
|
itemCounter += 1
|
|
186
209
|
byteCounter += item.length
|
|
187
210
|
|
|
@@ -218,14 +241,15 @@ class TextMapPropagator {
|
|
|
218
241
|
|
|
219
242
|
const tags = []
|
|
220
243
|
|
|
221
|
-
for (const key
|
|
222
|
-
|
|
223
|
-
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)) {
|
|
224
248
|
log.error('Trace tags from span are invalid, skipping injection.')
|
|
225
249
|
return
|
|
226
250
|
}
|
|
227
251
|
|
|
228
|
-
tags.push(`${key}=${
|
|
252
|
+
tags.push(`${key}=${value}`)
|
|
229
253
|
}
|
|
230
254
|
|
|
231
255
|
const header = tags.join(',')
|
|
@@ -275,7 +299,7 @@ class TextMapPropagator {
|
|
|
275
299
|
const {
|
|
276
300
|
_sampling: { priority, mechanism },
|
|
277
301
|
_tracestate: ts = new TraceState(),
|
|
278
|
-
_trace: { origin, tags },
|
|
302
|
+
_trace: { origin, tags: traceTags },
|
|
279
303
|
} = spanContext
|
|
280
304
|
|
|
281
305
|
carrier[traceparentKey] = spanContext.toTraceparent()
|
|
@@ -297,21 +321,22 @@ class TextMapPropagator {
|
|
|
297
321
|
if (typeof origin === 'string') {
|
|
298
322
|
const originValue = origin
|
|
299
323
|
.replaceAll(tracestateOriginFilter, '_')
|
|
300
|
-
.replaceAll(
|
|
324
|
+
.replaceAll('=', '~')
|
|
301
325
|
|
|
302
326
|
state.set('o', originValue)
|
|
303
327
|
}
|
|
304
328
|
|
|
305
|
-
for (const key
|
|
306
|
-
|
|
329
|
+
for (const key of Object.keys(traceTags)) {
|
|
330
|
+
const tagValueRaw = traceTags[key]
|
|
331
|
+
if (!tagValueRaw || !key.startsWith('_dd.p.')) continue
|
|
307
332
|
|
|
308
333
|
const tagKey = 't.' + key.slice(6)
|
|
309
334
|
.replaceAll(tracestateTagKeyFilter, '_')
|
|
310
335
|
|
|
311
|
-
const tagValue =
|
|
336
|
+
const tagValue = tagValueRaw
|
|
312
337
|
.toString()
|
|
313
338
|
.replaceAll(tracestateTagValueFilter, '_')
|
|
314
|
-
.replaceAll(
|
|
339
|
+
.replaceAll('=', '~')
|
|
315
340
|
|
|
316
341
|
state.set(tagKey, tagValue)
|
|
317
342
|
}
|
|
@@ -410,9 +435,11 @@ class TextMapPropagator {
|
|
|
410
435
|
}
|
|
411
436
|
|
|
412
437
|
if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'ignore') {
|
|
413
|
-
context
|
|
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 = []
|
|
414
441
|
} else {
|
|
415
|
-
if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart') {
|
|
442
|
+
if (this._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT === 'restart' && context) {
|
|
416
443
|
context._links = []
|
|
417
444
|
context._links.push({
|
|
418
445
|
context,
|
|
@@ -503,14 +530,19 @@ class TextMapPropagator {
|
|
|
503
530
|
|
|
504
531
|
_extractTraceparentContext (carrier) {
|
|
505
532
|
const headerValue = carrier[traceparentKey]
|
|
506
|
-
if (
|
|
533
|
+
if (typeof headerValue !== 'string') {
|
|
507
534
|
return null
|
|
508
535
|
}
|
|
509
536
|
const matches = headerValue.trim().match(traceparentExpr)
|
|
510
|
-
if (matches
|
|
511
|
-
const [version, traceId, spanId, flags, tail] = matches
|
|
537
|
+
if (matches !== null) {
|
|
538
|
+
const [, version, traceId, spanId, flags, tail] = matches
|
|
512
539
|
const traceparent = { version }
|
|
513
|
-
|
|
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)
|
|
514
546
|
if (invalidSegment.test(traceId)) return null
|
|
515
547
|
if (invalidSegment.test(spanId)) return null
|
|
516
548
|
|
|
@@ -524,7 +556,7 @@ class TextMapPropagator {
|
|
|
524
556
|
traceId: id(traceId, 16),
|
|
525
557
|
spanId: id(spanId, 16),
|
|
526
558
|
isRemote: true,
|
|
527
|
-
sampling: { priority: Number.parseInt(flags,
|
|
559
|
+
sampling: { priority: Number.parseInt(flags, 16) & 1 ? 1 : 0 },
|
|
528
560
|
traceparent,
|
|
529
561
|
tracestate,
|
|
530
562
|
})
|
|
@@ -550,7 +582,7 @@ class TextMapPropagator {
|
|
|
550
582
|
break
|
|
551
583
|
}
|
|
552
584
|
case 'o':
|
|
553
|
-
spanContext._trace.origin = value
|
|
585
|
+
spanContext._trace.origin = value.replaceAll('~', '=')
|
|
554
586
|
break
|
|
555
587
|
case 't.dm': {
|
|
556
588
|
const mechanism = Math.abs(Number.parseInt(value, 10))
|
|
@@ -563,7 +595,7 @@ class TextMapPropagator {
|
|
|
563
595
|
default: {
|
|
564
596
|
if (!key.startsWith('t.')) continue
|
|
565
597
|
const subKey = key.slice(2) // e.g. t.tid -> tid
|
|
566
|
-
const transformedValue = value.replaceAll(
|
|
598
|
+
const transformedValue = value.replaceAll('~', '=')
|
|
567
599
|
|
|
568
600
|
// If subkey is tid then do nothing because trace header tid should always be preserved
|
|
569
601
|
if (subKey === 'tid') {
|
|
@@ -681,7 +713,7 @@ class TextMapPropagator {
|
|
|
681
713
|
if (!header) return
|
|
682
714
|
|
|
683
715
|
const baggages = header.split(',')
|
|
684
|
-
const baggageTagKeys =
|
|
716
|
+
const baggageTagKeys = this.#getBaggageTagKeysSet()
|
|
685
717
|
const tagAllKeys = baggageTagKeys.has('*')
|
|
686
718
|
/** @type {Record<string, string> | undefined} */
|
|
687
719
|
let items
|
|
@@ -721,11 +753,17 @@ class TextMapPropagator {
|
|
|
721
753
|
tracerMetrics.count('context_header_style.malformed', ['header_style:baggage']).inc()
|
|
722
754
|
return
|
|
723
755
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
+
}
|
|
729
767
|
}
|
|
730
768
|
items ??= {}
|
|
731
769
|
items[key] = value
|
|
@@ -15,8 +15,7 @@ const WHITESPACE = /[ \t]/
|
|
|
15
15
|
* @returns {[string, string][]} Entries in reverse of wire order.
|
|
16
16
|
*/
|
|
17
17
|
function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
|
|
18
|
-
const segments = value.split(fieldSeparator)
|
|
19
|
-
segments.length = Math.min(segments.length, MAX_LIST_MEMBERS)
|
|
18
|
+
const segments = value.split(fieldSeparator, MAX_LIST_MEMBERS)
|
|
20
19
|
|
|
21
20
|
// TODO: We should extract dd no matter at what position and move it to the front of the list.
|
|
22
21
|
// Extract up 31 additional entries.
|
|
@@ -57,28 +56,45 @@ function toString (map, pairSeparator, fieldSeparator) {
|
|
|
57
56
|
return result
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
class TraceStateData
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
class TraceStateData {
|
|
60
|
+
#map
|
|
61
|
+
changed = false
|
|
62
|
+
|
|
63
|
+
constructor (entries) {
|
|
64
|
+
this.#map = entries ? new Map(entries) : new Map()
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
set (
|
|
67
|
-
if (this.
|
|
68
|
-
return
|
|
69
|
-
}
|
|
67
|
+
set (key, value) {
|
|
68
|
+
if (this.#map.get(key) === value && (value !== undefined || this.#map.has(key))) return this
|
|
70
69
|
this.changed = true
|
|
71
|
-
|
|
70
|
+
this.#map.set(key, value)
|
|
71
|
+
return this
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
get (key) {
|
|
75
|
+
return this.#map.get(key)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
delete (key) {
|
|
75
79
|
this.changed = true
|
|
76
|
-
return
|
|
80
|
+
return this.#map.delete(key)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
clear (
|
|
83
|
+
clear () {
|
|
80
84
|
this.changed = true
|
|
81
|
-
|
|
85
|
+
this.#map.clear()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
entries () {
|
|
89
|
+
return this.#map.entries()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[Symbol.iterator] () {
|
|
93
|
+
return this.#map[Symbol.iterator]()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get size () {
|
|
97
|
+
return this.#map.size
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
static fromString (value) {
|
|
@@ -94,25 +110,45 @@ class TraceStateData extends Map {
|
|
|
94
110
|
* Pairs are stored in reverse of the serialized format to rely on set ordering
|
|
95
111
|
* new entries at the end to express update movement.
|
|
96
112
|
*/
|
|
97
|
-
class TraceState
|
|
113
|
+
class TraceState {
|
|
114
|
+
#map
|
|
115
|
+
|
|
116
|
+
constructor (entries) {
|
|
117
|
+
this.#map = entries ? new Map(entries) : new Map()
|
|
118
|
+
}
|
|
119
|
+
|
|
98
120
|
// Delete entries on update to ensure they're moved to the end of the list
|
|
99
121
|
set (key, value) {
|
|
100
|
-
if (this.has(key))
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
if (this.#map.has(key)) this.#map.delete(key)
|
|
123
|
+
this.#map.set(key, value)
|
|
124
|
+
return this
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get (key) {
|
|
128
|
+
return this.#map.get(key)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
delete (key) {
|
|
132
|
+
return this.#map.delete(key)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[Symbol.iterator] () {
|
|
136
|
+
return this.#map[Symbol.iterator]()
|
|
137
|
+
}
|
|
103
138
|
|
|
104
|
-
|
|
139
|
+
get size () {
|
|
140
|
+
return this.#map.size
|
|
105
141
|
}
|
|
106
142
|
|
|
107
143
|
forVendor (vendor, handle) {
|
|
108
|
-
const data =
|
|
144
|
+
const data = this.#map.get(vendor)
|
|
109
145
|
const state = TraceStateData.fromString(data)
|
|
110
146
|
const result = handle(state)
|
|
111
147
|
|
|
112
148
|
if (state.changed) {
|
|
113
149
|
const value = state.toString()
|
|
114
150
|
if (value) {
|
|
115
|
-
this.set(vendor,
|
|
151
|
+
this.set(vendor, value)
|
|
116
152
|
} else {
|
|
117
153
|
this.delete(vendor)
|
|
118
154
|
}
|
|
@@ -11,21 +11,16 @@ const runtimeMetrics = require('../runtime_metrics')
|
|
|
11
11
|
const log = require('../log')
|
|
12
12
|
const { storage } = require('../../../datadog-core')
|
|
13
13
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
14
|
-
const { getValueFromEnvSources } = require('../config/helper')
|
|
15
|
-
const { isTrue } = require('../util')
|
|
16
14
|
const SpanContext = require('./span_context')
|
|
17
15
|
|
|
18
16
|
const dateNow = Date.now
|
|
19
17
|
|
|
20
18
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
21
19
|
|
|
22
|
-
const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING'))
|
|
23
|
-
const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = isTrue(getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS'))
|
|
24
|
-
|
|
25
20
|
const unfinishedRegistry = createRegistry('unfinished')
|
|
26
21
|
const finishedRegistry = createRegistry('finished')
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
let OTEL_ENABLED = false
|
|
29
24
|
const ALLOWED = new Set(['string', 'number', 'boolean'])
|
|
30
25
|
|
|
31
26
|
const integrationCounters = {
|
|
@@ -37,6 +32,25 @@ const startCh = channel('dd-trace:span:start')
|
|
|
37
32
|
const finishCh = channel('dd-trace:span:finish')
|
|
38
33
|
const tagsUpdateCh = channel('dd-trace:span:tags:update')
|
|
39
34
|
|
|
35
|
+
// Module-scope so we don't allocate a fresh recursive closure on every
|
|
36
|
+
// `addLink` / `addEvent`.
|
|
37
|
+
/**
|
|
38
|
+
* @param {Record<string, string>} out
|
|
39
|
+
* @param {string} key
|
|
40
|
+
* @param {unknown} value
|
|
41
|
+
*/
|
|
42
|
+
function addArrayOrScalarAttribute (out, key, value) {
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (let i = 0; i < value.length; i++) {
|
|
45
|
+
addArrayOrScalarAttribute(out, `${key}.${i}`, value[i])
|
|
46
|
+
}
|
|
47
|
+
} else if (ALLOWED.has(typeof value)) {
|
|
48
|
+
out[key] = typeof value === 'string' ? value : String(value)
|
|
49
|
+
} else {
|
|
50
|
+
log.warn('Dropping span link attribute. It is not of an allowed type')
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
function getIntegrationCounter (event, integration) {
|
|
41
55
|
const counters = integrationCounters[event]
|
|
42
56
|
|
|
@@ -55,15 +69,22 @@ function getIntegrationCounter (event, integration) {
|
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
class DatadogSpan {
|
|
72
|
+
#parentTracer
|
|
73
|
+
|
|
58
74
|
constructor (tracer, processor, prioritySampler, fields, debug) {
|
|
75
|
+
OTEL_ENABLED = tracer._config.DD_TRACE_OTEL_ENABLED
|
|
76
|
+
|
|
59
77
|
const operationName = fields.operationName
|
|
60
78
|
const parent = fields.parent || null
|
|
61
|
-
//
|
|
79
|
+
// Stay on `Object.assign({}, src)` for backportability: V8 12+ (Node 22 /
|
|
80
|
+
// 24) inlines `{ ...src }` and beats `Object.assign` here, but on V8 10.2
|
|
81
|
+
// / 11.3 (Node 18 / 20) the spread takes a generic runtime path and slows
|
|
82
|
+
// `spans-finish-*` by ~140%. Revisit once those LTS lines drop.
|
|
62
83
|
// eslint-disable-next-line prefer-object-spread
|
|
63
84
|
const tags = Object.assign({}, fields.tags)
|
|
64
85
|
const hostname = fields.hostname
|
|
65
86
|
|
|
66
|
-
this
|
|
87
|
+
this.#parentTracer = tracer
|
|
67
88
|
this._debug = debug
|
|
68
89
|
this._processor = processor
|
|
69
90
|
this._prioritySampler = prioritySampler
|
|
@@ -94,7 +115,7 @@ class DatadogSpan {
|
|
|
94
115
|
attributes: this._sanitizeAttributes(link.attributes),
|
|
95
116
|
})) ?? []
|
|
96
117
|
|
|
97
|
-
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
118
|
+
if (this.#parentTracer._config.DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
98
119
|
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
99
120
|
runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
|
|
100
121
|
|
|
@@ -104,16 +125,8 @@ class DatadogSpan {
|
|
|
104
125
|
unfinishedRegistry.register(this, operationName, this)
|
|
105
126
|
}
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// constructor which still succeed today. Part of the problem is that `Span`
|
|
110
|
-
// stores only the tracer and not the config, so anything that needs the
|
|
111
|
-
// config has to read it from the tracer stored on the span, including
|
|
112
|
-
// even `Span` itself in this case.
|
|
113
|
-
//
|
|
114
|
-
// TODO: Refactor Tracer/Span + tests to avoid having to do nullish checks.
|
|
115
|
-
if (tracer?._config?.DD_TRACE_SPAN_LEAK_DEBUG > 0) {
|
|
116
|
-
require('../spanleak').addSpan(this, operationName)
|
|
128
|
+
if (tracer._config.DD_TRACE_SPAN_LEAK_DEBUG > 0) {
|
|
129
|
+
require('../spanleak').addSpan(this)
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
if (startCh.hasSubscribers) {
|
|
@@ -124,7 +137,7 @@ class DatadogSpan {
|
|
|
124
137
|
[util.inspect.custom] () {
|
|
125
138
|
return {
|
|
126
139
|
...this,
|
|
127
|
-
|
|
140
|
+
parentTracer: `[${this.#parentTracer.constructor.name}]`,
|
|
128
141
|
_prioritySampler: `[${this._prioritySampler.constructor.name}]`,
|
|
129
142
|
_processor: `[${this._processor.constructor.name}]`,
|
|
130
143
|
}
|
|
@@ -156,7 +169,7 @@ class DatadogSpan {
|
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
tracer () {
|
|
159
|
-
return this
|
|
172
|
+
return this.#parentTracer
|
|
160
173
|
}
|
|
161
174
|
|
|
162
175
|
setOperationName (name) {
|
|
@@ -254,14 +267,14 @@ class DatadogSpan {
|
|
|
254
267
|
return
|
|
255
268
|
}
|
|
256
269
|
|
|
257
|
-
if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING && !this._spanContext._tags['service.name']) {
|
|
270
|
+
if (this.#parentTracer._config.DD_TRACE_EXPERIMENTAL_STATE_TRACKING && !this._spanContext._tags['service.name']) {
|
|
258
271
|
log.error('Finishing invalid span: %s', this)
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
getIntegrationCounter('spans_finished', this._integrationName).inc()
|
|
262
275
|
this._spanContext._tags['_dd.integration'] = this._integrationName
|
|
263
276
|
|
|
264
|
-
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
277
|
+
if (this.#parentTracer._config.DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
265
278
|
runtimeMetrics.decrement('runtime.node.spans.unfinished')
|
|
266
279
|
runtimeMetrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
|
|
267
280
|
runtimeMetrics.increment('runtime.node.spans.finished')
|
|
@@ -274,7 +287,11 @@ class DatadogSpan {
|
|
|
274
287
|
finishedRegistry.register(this, this._name)
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
|
|
290
|
+
// Dominant call site is `span.finish()` with no argument; skip the
|
|
291
|
+
// `Number.parseFloat` round-trip for the undefined case.
|
|
292
|
+
finishTime = finishTime === undefined
|
|
293
|
+
? this._getTime()
|
|
294
|
+
: (Number.parseFloat(finishTime) || this._getTime())
|
|
278
295
|
|
|
279
296
|
this._duration = finishTime - this._startTime
|
|
280
297
|
this._spanContext._trace.finished.push(this)
|
|
@@ -283,38 +300,29 @@ class DatadogSpan {
|
|
|
283
300
|
this._processor.process(this)
|
|
284
301
|
}
|
|
285
302
|
|
|
303
|
+
/**
|
|
304
|
+
* @param {Record<string, unknown>} [attributes]
|
|
305
|
+
*/
|
|
286
306
|
_sanitizeAttributes (attributes = {}) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
for (const subkey in maybeArray) {
|
|
292
|
-
addArrayOrScalarAttributes(`${key}.${subkey}`, maybeArray[subkey])
|
|
293
|
-
}
|
|
294
|
-
} else {
|
|
295
|
-
const maybeScalar = maybeArray
|
|
296
|
-
if (ALLOWED.has(typeof maybeScalar)) {
|
|
297
|
-
// Wrap the value as a string if it's not already a string
|
|
298
|
-
sanitizedAttributes[key] = typeof maybeScalar === 'string' ? maybeScalar : String(maybeScalar)
|
|
299
|
-
} else {
|
|
300
|
-
log.warn('Dropping span link attribute. It is not of an allowed type')
|
|
301
|
-
}
|
|
302
|
-
}
|
|
307
|
+
/** @type {Record<string, string>} */
|
|
308
|
+
const out = {}
|
|
309
|
+
for (const key of Object.keys(attributes)) {
|
|
310
|
+
addArrayOrScalarAttribute(out, key, attributes[key])
|
|
303
311
|
}
|
|
304
|
-
|
|
305
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
306
|
-
addArrayOrScalarAttributes(key, value)
|
|
307
|
-
}
|
|
308
|
-
return sanitizedAttributes
|
|
312
|
+
return out
|
|
309
313
|
}
|
|
310
314
|
|
|
315
|
+
/**
|
|
316
|
+
* @param {Record<string, unknown>} [attributes]
|
|
317
|
+
*/
|
|
311
318
|
_sanitizeEventAttributes (attributes = {}) {
|
|
312
319
|
const sanitizedAttributes = {}
|
|
313
320
|
|
|
314
|
-
for (const
|
|
321
|
+
for (const key of Object.keys(attributes)) {
|
|
322
|
+
const value = attributes[key]
|
|
315
323
|
if (Array.isArray(value)) {
|
|
316
324
|
const newArray = []
|
|
317
|
-
for (const subvalue of
|
|
325
|
+
for (const subvalue of value) {
|
|
318
326
|
if (ALLOWED.has(typeof subvalue)) {
|
|
319
327
|
newArray.push(subvalue)
|
|
320
328
|
} else {
|
|
@@ -336,7 +344,7 @@ class DatadogSpan {
|
|
|
336
344
|
let startTime
|
|
337
345
|
|
|
338
346
|
let baggage = {}
|
|
339
|
-
const propagationBehavior = this.
|
|
347
|
+
const propagationBehavior = this.#parentTracer._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
|
|
340
348
|
if (parent && parent._isRemote && propagationBehavior !== 'continue') {
|
|
341
349
|
baggage = parent._baggageItems
|
|
342
350
|
parent = null
|
|
@@ -148,9 +148,8 @@ class PrioritySampler {
|
|
|
148
148
|
update (rates) {
|
|
149
149
|
const samplers = {}
|
|
150
150
|
|
|
151
|
-
for (const key
|
|
152
|
-
|
|
153
|
-
samplers[key] = new Sampler(rate)
|
|
151
|
+
for (const key of Object.keys(rates)) {
|
|
152
|
+
samplers[key] = new Sampler(rates[key])
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler
|
|
@@ -334,7 +333,10 @@ class PrioritySampler {
|
|
|
334
333
|
if (!trace.tags[DECISION_MAKER_KEY]) {
|
|
335
334
|
trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
|
|
336
335
|
}
|
|
337
|
-
} else {
|
|
336
|
+
} else if (DECISION_MAKER_KEY in trace.tags) {
|
|
337
|
+
// Guard the `delete` so the common drop path doesn't pay the V8
|
|
338
|
+
// dictionary-mode transition unless a prior keep decision actually
|
|
339
|
+
// set the tag.
|
|
338
340
|
delete trace.tags[DECISION_MAKER_KEY]
|
|
339
341
|
}
|
|
340
342
|
}
|
|
@@ -4,6 +4,7 @@ const path = require('path')
|
|
|
4
4
|
const { pathToFileURL } = require('url')
|
|
5
5
|
|
|
6
6
|
const satisfies = require('../../../../vendor/dist/semifies')
|
|
7
|
+
const getGitMetadata = require('../git_metadata')
|
|
7
8
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
8
9
|
const { getIsAzureFunction } = require('../serverless')
|
|
9
10
|
const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
|
|
@@ -38,10 +39,10 @@ class Config {
|
|
|
38
39
|
...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
this.tags[GIT_REPOSITORY_URL] =
|
|
44
|
-
this.tags[GIT_COMMIT_SHA] =
|
|
42
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(options)
|
|
43
|
+
if (repositoryUrl && commitSHA) {
|
|
44
|
+
this.tags[GIT_REPOSITORY_URL] = repositoryUrl
|
|
45
|
+
this.tags[GIT_COMMIT_SHA] = commitSHA
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// Normalize from seconds to milliseconds. Default must be longer than a minute.
|
|
@@ -5,6 +5,7 @@ const tracerVersion = require('../../../../package.json').version
|
|
|
5
5
|
const request = require('../exporters/common/request')
|
|
6
6
|
const log = require('../log')
|
|
7
7
|
const { getExtraServices } = require('../service-naming/extra-services')
|
|
8
|
+
const getGitMetadata = require('../git_metadata')
|
|
8
9
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
9
10
|
const tagger = require('../tagger')
|
|
10
11
|
const { getAgentUrl } = require('../agent/url')
|
|
@@ -37,11 +38,12 @@ class RemoteConfig {
|
|
|
37
38
|
'_dd.rc.client_id': clientId,
|
|
38
39
|
})
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
42
|
+
const tags = repositoryUrl
|
|
41
43
|
? {
|
|
42
44
|
...config.tags,
|
|
43
|
-
[GIT_REPOSITORY_URL]:
|
|
44
|
-
[GIT_COMMIT_SHA]:
|
|
45
|
+
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
46
|
+
[GIT_COMMIT_SHA]: commitSHA,
|
|
45
47
|
}
|
|
46
48
|
: config.tags
|
|
47
49
|
|