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.
Files changed (64) hide show
  1. package/index.d.ts +14 -0
  2. package/package.json +5 -5
  3. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  4. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  5. package/packages/datadog-instrumentations/src/jest.js +62 -32
  6. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  7. package/packages/datadog-instrumentations/src/mocha/main.js +25 -4
  8. package/packages/datadog-instrumentations/src/mocha/worker.js +5 -2
  9. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  10. package/packages/datadog-plugin-bullmq/src/consumer.js +2 -2
  11. package/packages/datadog-plugin-bullmq/src/producer.js +14 -20
  12. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
  13. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  14. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  15. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  16. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  17. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  18. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  19. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  20. package/packages/dd-trace/src/config/index.js +1 -55
  21. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  22. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  23. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  24. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  25. package/packages/dd-trace/src/datastreams/size.js +6 -2
  26. package/packages/dd-trace/src/debugger/config.js +5 -2
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  28. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  29. package/packages/dd-trace/src/dogstatsd.js +10 -7
  30. package/packages/dd-trace/src/encode/0.4.js +2 -2
  31. package/packages/dd-trace/src/encode/0.5.js +2 -2
  32. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  33. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  34. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  35. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  36. package/packages/dd-trace/src/git_metadata.js +66 -0
  37. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  38. package/packages/dd-trace/src/id.js +15 -26
  39. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  40. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  41. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  42. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  43. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  44. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  45. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  46. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  47. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  48. package/packages/dd-trace/src/llmobs/util.js +80 -5
  49. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  50. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  51. package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -2
  52. package/packages/dd-trace/src/opentelemetry/span-helpers.js +188 -50
  53. package/packages/dd-trace/src/opentelemetry/span.js +42 -80
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +65 -27
  55. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +58 -22
  56. package/packages/dd-trace/src/opentracing/span.js +56 -48
  57. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  58. package/packages/dd-trace/src/priority_sampler.js +6 -4
  59. package/packages/dd-trace/src/profiling/config.js +5 -4
  60. package/packages/dd-trace/src/remote_config/index.js +5 -3
  61. package/packages/dd-trace/src/span_format.js +52 -5
  62. package/packages/dd-trace/src/span_processor.js +0 -4
  63. package/packages/dd-trace/src/spanleak.js +0 -1
  64. 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 [key, value] of Object.entries(baggageItems)) {
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(value)},`
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 in trace.tags) {
222
- if (!trace.tags[key] || !key.startsWith('_dd.p.')) continue
223
- if (!this._validateTagKey(key) || !this._validateTagValue(trace.tags[key])) {
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}=${trace.tags[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(/[\x3D]/g, '~')
324
+ .replaceAll('=', '~')
301
325
 
302
326
  state.set('o', originValue)
303
327
  }
304
328
 
305
- for (const key in tags) {
306
- if (!tags[key] || !key.startsWith('_dd.p.')) continue
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 = tags[key]
336
+ const tagValue = tagValueRaw
312
337
  .toString()
313
338
  .replaceAll(tracestateTagValueFilter, '_')
314
- .replaceAll(/[\x3D]/g, '~')
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._links = []
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 (!headerValue) {
533
+ if (typeof headerValue !== 'string') {
507
534
  return null
508
535
  }
509
536
  const matches = headerValue.trim().match(traceparentExpr)
510
- if (matches?.length) {
511
- const [version, traceId, spanId, flags, tail] = matches.slice(1)
537
+ if (matches !== null) {
538
+ const [, version, traceId, spanId, flags, tail] = matches
512
539
  const traceparent = { version }
513
- const tracestate = TraceState.fromString(carrier.tracestate)
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, 10) & 1 ? 1 : 0 },
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(/[\x7E]/gm, '=')
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 = new Set(this._config.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
- try {
725
- value = decodeURIComponent(value)
726
- } catch {
727
- const bytes = value.replaceAll(percentByte, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
728
- value = Buffer.from(bytes, 'binary').toString('utf8')
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 extends Map {
61
- constructor (...args) {
62
- super(...args)
63
- this.changed = false
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 (...args) {
67
- if (this.has(args[0]) && this.get(args[0]) === args[1]) {
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
- return super.set(...args)
70
+ this.#map.set(key, value)
71
+ return this
72
72
  }
73
73
 
74
- delete (...args) {
74
+ get (key) {
75
+ return this.#map.get(key)
76
+ }
77
+
78
+ delete (key) {
75
79
  this.changed = true
76
- return super.delete(...args)
80
+ return this.#map.delete(key)
77
81
  }
78
82
 
79
- clear (...args) {
83
+ clear () {
80
84
  this.changed = true
81
- return super.clear(...args)
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 extends Map {
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
- this.delete(key)
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
- return super.set(key, value)
139
+ get size () {
140
+ return this.#map.size
105
141
  }
106
142
 
107
143
  forVendor (vendor, handle) {
108
- const data = super.get(vendor)
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, state.toString())
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
- const OTEL_ENABLED = !!getValueFromEnvSources('DD_TRACE_OTEL_ENABLED')
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
- // TODO(BridgeAR): Investigate why this is causing a performance regression
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._parentTracer = tracer
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
- // Nullish operator is used here because both `tracer` and `tracer._config`
108
- // can be null and there are tests passing invalid values to the `Span`
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
- _parentTracer: `[${this._parentTracer.constructor.name}]`,
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._parentTracer
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
- finishTime = Number.parseFloat(finishTime) || this._getTime()
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
- const sanitizedAttributes = {}
288
-
289
- const addArrayOrScalarAttributes = (key, maybeArray) => {
290
- if (Array.isArray(maybeArray)) {
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 [key, value] of Object.entries(attributes)) {
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 Object.values(value)) {
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._parentTracer?._config?.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
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
@@ -30,6 +30,7 @@ class DatadogSpanContext {
30
30
  tags: {},
31
31
  }
32
32
  this._otelSpanContext = undefined
33
+ this._otelActiveSpan = undefined
33
34
  }
34
35
 
35
36
  [util.inspect.custom] () {
@@ -148,9 +148,8 @@ class PrioritySampler {
148
148
  update (rates) {
149
149
  const samplers = {}
150
150
 
151
- for (const key in rates) {
152
- const rate = rates[key]
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
- // Add source code integration tags if available
42
- if (options.repositoryUrl && options.commitSHA) {
43
- this.tags[GIT_REPOSITORY_URL] = options.repositoryUrl
44
- this.tags[GIT_COMMIT_SHA] = options.commitSHA
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 tags = config.repositoryUrl
41
+ const { commitSHA, repositoryUrl } = getGitMetadata(config)
42
+ const tags = repositoryUrl
41
43
  ? {
42
44
  ...config.tags,
43
- [GIT_REPOSITORY_URL]: config.repositoryUrl,
44
- [GIT_COMMIT_SHA]: config.commitSHA,
45
+ [GIT_REPOSITORY_URL]: repositoryUrl,
46
+ [GIT_COMMIT_SHA]: commitSHA,
45
47
  }
46
48
  : config.tags
47
49