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.
Files changed (101) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +14 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  5. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  6. package/packages/datadog-instrumentations/src/express.js +3 -2
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/hono.js +15 -4
  9. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  10. package/packages/datadog-instrumentations/src/jest.js +146 -90
  11. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  12. package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
  15. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  16. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  17. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  18. package/packages/datadog-instrumentations/src/router.js +53 -33
  19. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  22. package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
  23. package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
  25. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  26. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  27. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  28. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  32. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  33. package/packages/datadog-plugin-router/src/index.js +13 -0
  34. package/packages/dd-trace/index.js +4 -3
  35. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  36. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  37. package/packages/dd-trace/src/baggage.js +10 -0
  38. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  39. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  40. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  41. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  42. package/packages/dd-trace/src/config/index.js +7 -60
  43. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  45. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  46. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  47. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  48. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  49. package/packages/dd-trace/src/datastreams/size.js +6 -2
  50. package/packages/dd-trace/src/debugger/config.js +6 -3
  51. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  52. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  53. package/packages/dd-trace/src/dogstatsd.js +10 -7
  54. package/packages/dd-trace/src/encode/0.4.js +3 -3
  55. package/packages/dd-trace/src/encode/0.5.js +2 -2
  56. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  57. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  58. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  59. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  60. package/packages/dd-trace/src/git_metadata.js +66 -0
  61. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  62. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  63. package/packages/dd-trace/src/id.js +15 -26
  64. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  65. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  66. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  68. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  69. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  70. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  71. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  72. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  73. package/packages/dd-trace/src/llmobs/util.js +80 -5
  74. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  75. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  76. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
  78. package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
  79. package/packages/dd-trace/src/opentelemetry/span.js +42 -108
  80. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  81. package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
  82. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
  83. package/packages/dd-trace/src/opentracing/span.js +58 -49
  84. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  85. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  86. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  87. package/packages/dd-trace/src/priority_sampler.js +6 -4
  88. package/packages/dd-trace/src/profiling/config.js +5 -4
  89. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  90. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  91. package/packages/dd-trace/src/proxy.js +3 -3
  92. package/packages/dd-trace/src/remote_config/index.js +5 -3
  93. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  94. package/packages/dd-trace/src/span_format.js +52 -5
  95. package/packages/dd-trace/src/span_processor.js +1 -5
  96. package/packages/dd-trace/src/spanleak.js +0 -1
  97. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  98. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  99. package/packages/dd-trace/src/util.js +17 -0
  100. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  101. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -1,19 +1,48 @@
1
1
  'use strict'
2
2
 
3
- const traceStateRegex = /[ \t]*([^=]+)=([ \t]*[^, \t]+)[ \t]*(,|$)/gim
4
- const traceStateDataRegex = /([^:]+):([^;]+)(;|$)/gim
3
+ // W3C Trace Context §3.3.1.2: max 32 list-members.
4
+ // https://www.w3.org/TR/trace-context/#tracestate-header-field-values
5
+ const MAX_LIST_MEMBERS = 32
6
+ const WHITESPACE = /[ \t]/
5
7
 
6
- function fromString (Type, regex, value) {
7
- if (typeof value !== 'string' || !value.length) {
8
- return new Type()
8
+ /**
9
+ * Parse a separator-delimited string into key/value entries.
10
+ *
11
+ * @param {string} value
12
+ * @param {string} fieldSeparator Between entries.
13
+ * @param {string} pairSeparator Between key and value within an entry.
14
+ * @param {boolean} rejectValueTabs Drop entries whose value contains an internal tab.
15
+ * @returns {[string, string][]} Entries in reverse of wire order.
16
+ */
17
+ function parseEntries (value, fieldSeparator, pairSeparator, rejectValueTabs) {
18
+ const segments = value.split(fieldSeparator, MAX_LIST_MEMBERS)
19
+
20
+ // TODO: We should extract dd no matter at what position and move it to the front of the list.
21
+ // Extract up 31 additional entries.
22
+ const entries = []
23
+ for (let index = 0; index < segments.length; index++) {
24
+ const segment = segments[index]
25
+ const splitIndex = segment.indexOf(pairSeparator)
26
+ if (splitIndex === -1) continue
27
+ const key = segment.slice(0, splitIndex).trim()
28
+ if (!key || WHITESPACE.test(key)) continue
29
+ // W3C §3.3.1.3.2: value = 0*255(chr) nblk-chr; chr = %x20 / nblk-chr (no tab).
30
+ // Leading 0x20 is part of value; trailing whitespace is OWS.
31
+ const entryValue = segment.slice(splitIndex + 1).trimEnd()
32
+ if (!entryValue || rejectValueTabs && entryValue.includes('\t')) continue
33
+ entries.push([key, entryValue])
9
34
  }
35
+ // Reverse so the Map's insertion order is reverse of wire order. `toString`
36
+ // prepends as it iterates, which yields the original wire order back.
37
+ entries.reverse()
38
+ return entries
39
+ }
10
40
 
11
- const values = []
12
- for (const row of value.matchAll(regex)) {
13
- values.unshift(row.slice(1, 3))
41
+ function fromString (Type, value, fieldSeparator, pairSeparator, rejectValueTabs) {
42
+ if (typeof value !== 'string' || !value.length) {
43
+ return new Type()
14
44
  }
15
-
16
- return new Type(values)
45
+ return new Type(parseEntries(value, fieldSeparator, pairSeparator, rejectValueTabs))
17
46
  }
18
47
 
19
48
  function toString (map, pairSeparator, fieldSeparator) {
@@ -27,32 +56,49 @@ function toString (map, pairSeparator, fieldSeparator) {
27
56
  return result
28
57
  }
29
58
 
30
- class TraceStateData extends Map {
31
- constructor (...args) {
32
- super(...args)
33
- this.changed = false
59
+ class TraceStateData {
60
+ #map
61
+ changed = false
62
+
63
+ constructor (entries) {
64
+ this.#map = entries ? new Map(entries) : new Map()
34
65
  }
35
66
 
36
- set (...args) {
37
- if (this.has(args[0]) && this.get(args[0]) === args[1]) {
38
- return
39
- }
67
+ set (key, value) {
68
+ if (this.#map.get(key) === value && (value !== undefined || this.#map.has(key))) return this
40
69
  this.changed = true
41
- return super.set(...args)
70
+ this.#map.set(key, value)
71
+ return this
42
72
  }
43
73
 
44
- delete (...args) {
74
+ get (key) {
75
+ return this.#map.get(key)
76
+ }
77
+
78
+ delete (key) {
45
79
  this.changed = true
46
- return super.delete(...args)
80
+ return this.#map.delete(key)
47
81
  }
48
82
 
49
- clear (...args) {
83
+ clear () {
50
84
  this.changed = true
51
- 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
52
98
  }
53
99
 
54
100
  static fromString (value) {
55
- return fromString(TraceStateData, traceStateDataRegex, value)
101
+ return fromString(TraceStateData, value, ';', ':', false)
56
102
  }
57
103
 
58
104
  toString () {
@@ -64,25 +110,45 @@ class TraceStateData extends Map {
64
110
  * Pairs are stored in reverse of the serialized format to rely on set ordering
65
111
  * new entries at the end to express update movement.
66
112
  */
67
- class TraceState extends Map {
113
+ class TraceState {
114
+ #map
115
+
116
+ constructor (entries) {
117
+ this.#map = entries ? new Map(entries) : new Map()
118
+ }
119
+
68
120
  // Delete entries on update to ensure they're moved to the end of the list
69
121
  set (key, value) {
70
- if (this.has(key)) {
71
- this.delete(key)
72
- }
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
+ }
73
138
 
74
- return super.set(key, value)
139
+ get size () {
140
+ return this.#map.size
75
141
  }
76
142
 
77
143
  forVendor (vendor, handle) {
78
- const data = super.get(vendor)
144
+ const data = this.#map.get(vendor)
79
145
  const state = TraceStateData.fromString(data)
80
146
  const result = handle(state)
81
147
 
82
148
  if (state.changed) {
83
149
  const value = state.toString()
84
150
  if (value) {
85
- this.set(vendor, state.toString())
151
+ this.set(vendor, value)
86
152
  } else {
87
153
  this.delete(vendor)
88
154
  }
@@ -92,7 +158,7 @@ class TraceState extends Map {
92
158
  }
93
159
 
94
160
  static fromString (value) {
95
- return fromString(TraceState, traceStateRegex, value)
161
+ return fromString(TraceState, value, ',', '=', true)
96
162
  }
97
163
 
98
164
  toString () {
@@ -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,8 @@ class DatadogSpan {
336
344
  let startTime
337
345
 
338
346
  let baggage = {}
339
- if (parent && parent._isRemote && this._parentTracer?._config?.tracePropagationBehaviorExtract !== 'continue') {
347
+ const propagationBehavior = this.#parentTracer._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
348
+ if (parent && parent._isRemote && propagationBehavior !== 'continue') {
340
349
  baggage = parent._baggageItems
341
350
  parent = null
342
351
  }
@@ -375,7 +384,7 @@ class DatadogSpan {
375
384
  .padEnd(16, '0')
376
385
  }
377
386
 
378
- if (this._parentTracer?._config?.tracePropagationBehaviorExtract === 'restart') {
387
+ if (propagationBehavior === 'restart') {
379
388
  spanContext._baggageItems = baggage
380
389
  }
381
390
  }
@@ -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] () {
@@ -103,42 +103,129 @@ function getGitHubEventPayload () {
103
103
  return JSON.parse(readFileSync(path, 'utf8'))
104
104
  }
105
105
 
106
- function getJobIDFromDiagFile (runnerTemp) {
107
- if (!runnerTemp || !existsSync(runnerTemp)) { return null }
106
+ const uniq = (items) => [...new Set(items)]
107
+
108
+ /**
109
+ * GitHub runner diagnostic logs live under the runner installation directory in `_diag`.
110
+ * On many runners, we can derive the installation directory from RUNNER_TEMP:
111
+ * <runnerRoot>/_work/_temp -> <runnerRoot>/_diag
112
+ *
113
+ * This is much more robust than relying on hardcoded paths, especially on self-hosted runners
114
+ * and GHES environments where the runner may be installed under arbitrary directories/users.
115
+ */
116
+ function getGithubDiagnosticDirsFromEnv (runnerTemp) {
117
+ const dirs = []
118
+
119
+ if (runnerTemp) {
120
+ // RUNNER_TEMP is typically: <runnerRoot>/_work/_temp
121
+ const runnerRoot = path.resolve(runnerTemp, '..', '..').replaceAll(path.sep, '/')
122
+ // Bounded-depth patterns cover every runner layout we've observed
123
+ // (including cached/<version>/_diag) without assuming a `cached` wrapper
124
+ // and without walking the whole tree.
125
+ dirs.push(
126
+ path.posix.join(runnerRoot, 'actions-runner', '_diag'),
127
+ `${runnerRoot}/actions-runner/*/_diag`,
128
+ `${runnerRoot}/actions-runner/*/*/_diag`,
129
+ path.posix.join(runnerRoot, '_diag'),
130
+ `${runnerRoot}/*/_diag`,
131
+ `${runnerRoot}/*/*/_diag`
132
+ )
133
+ }
108
134
 
109
- // RUNNER_TEMP usually looks like:
110
- // Linux/mac hosted: /home/runner/work/_temp
111
- // Windows hosted: C:\actions-runner\_work\_temp
112
- // Self-hosted (unix): /opt/actions-runner/_work/_temp
135
+ return uniq(dirs.filter(Boolean))
136
+ }
113
137
 
114
- const workDir = path.dirname(runnerTemp) // .../work or .../_work
115
- const runnerRoot = path.dirname(workDir) // /home/runner/ (runner root)
138
+ function hasMagicChars (str) {
139
+ return str.includes('*') || str.includes('?')
140
+ }
116
141
 
117
- const dirs = [
118
- path.join(runnerRoot, 'cached', '_diag'),
119
- path.join(runnerRoot, '_diag'),
120
- path.join(runnerRoot, 'actions-runner', 'cached', '_diag'),
121
- path.join(runnerRoot, 'actions-runner', '_diag'),
122
- ]
142
+ // Expands a glob pattern with only `*`/`?` at path-segment boundaries (no `**`)
143
+ // into matching concrete paths using readdirSync — no external dependency needed.
144
+ function expandGlobPattern (pattern) {
145
+ const parts = pattern.split(/[/\\]/)
146
+ const wildcardIdx = parts.findIndex(p => hasMagicChars(p))
147
+ if (wildcardIdx === -1) return [pattern]
123
148
 
124
- const isWin = process.platform === 'win32'
149
+ const prefix = parts.slice(0, wildcardIdx).join('/')
150
+ const results = []
125
151
 
126
- // Hardcoded fallbacks
127
- if (isWin) {
128
- dirs.push(
129
- 'C:/actions-runner/cached/_diag',
130
- 'C:/actions-runner/_diag',
131
- )
132
- } else {
133
- dirs.push(
134
- '/home/runner/actions-runner/cached/_diag',
135
- '/home/runner/actions-runner/_diag',
136
- '/opt/actions-runner/_diag',
137
- )
152
+ function walk (dir, segIdx) {
153
+ if (segIdx === parts.length) {
154
+ results.push(dir)
155
+ return
156
+ }
157
+ const seg = parts[segIdx]
158
+ if (!hasMagicChars(seg)) {
159
+ walk(`${dir}/${seg}`, segIdx + 1)
160
+ return
161
+ }
162
+ try {
163
+ const re = new RegExp(
164
+ '^' + seg.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll('*', String.raw`[^/\\]*`).replaceAll('?', String.raw`[^/\\]`) + '$'
165
+ )
166
+ for (const entry of readdirSync(dir)) {
167
+ if (re.test(entry)) {
168
+ walk(`${dir}/${entry}`, segIdx + 1)
169
+ }
170
+ }
171
+ } catch {
172
+ // directory doesn't exist or isn't accessible
173
+ }
138
174
  }
139
175
 
140
- // Remove duplicates
141
- const possibleDiagsPaths = [...new Set(dirs)]
176
+ walk(prefix, wildcardIdx)
177
+ return results
178
+ }
179
+
180
+ /**
181
+ * Expands a mixed list of literal directories and glob patterns into concrete
182
+ * directories. Literals pass through unchanged (existence is checked later).
183
+ */
184
+ function expandDiagnosticDirCandidates (candidates) {
185
+ const expanded = []
186
+ for (const candidate of candidates) {
187
+ if (hasMagicChars(candidate)) {
188
+ expanded.push(...expandGlobPattern(candidate))
189
+ } else {
190
+ expanded.push(candidate)
191
+ }
192
+ }
193
+
194
+ return uniq(expanded)
195
+ }
196
+
197
+ const githubWellKnownDiagnosticDirsUnix = [
198
+ '/home/runner/actions-runner/_diag',
199
+ '/opt/actions-runner/_diag',
200
+ ]
201
+ const githubWellKnownDiagnosticDirsWin = [
202
+ 'C:/actions-runner/_diag',
203
+ ]
204
+
205
+ // Glob patterns covering layouts that namespace `_diag` under one or two
206
+ // intermediate directories. This includes both observed SaaS layouts
207
+ // (<runnerRoot>/cached/_diag pre-2.334.0, <runnerRoot>/cached/<version>/_diag
208
+ // since v2.334.0) and hypothetical future layouts that follow the same shape
209
+ // without a `cached` wrapper (e.g. <runnerRoot>/<version>/_diag). Depth is
210
+ // bounded on purpose: `*` matches a single segment, so no filesystem walk.
211
+ const githubWellKnownDiagnosticDirPatternsUnix = [
212
+ '/home/runner/actions-runner/*/_diag',
213
+ '/home/runner/actions-runner/*/*/_diag',
214
+ ]
215
+ const githubWellKnownDiagnosticDirPatternsWin = ['C:/actions-runner/*/_diag', 'C:/actions-runner/*/*/_diag']
216
+
217
+ const githubJobIDRegex = /"job":\s*{[\s\S]*?"v"\s*:\s*(\d+)(?:\.0)?/
218
+
219
+ function getJobIDFromDiagFile () {
220
+ const runnerTemp = getValueFromEnvSources('RUNNER_TEMP')
221
+ if (!runnerTemp || !existsSync(runnerTemp)) { return null }
222
+
223
+ const isWin = process.platform === 'win32'
224
+ const patterns = isWin ? githubWellKnownDiagnosticDirPatternsWin : githubWellKnownDiagnosticDirPatternsUnix
225
+ const literals = isWin ? githubWellKnownDiagnosticDirsWin : githubWellKnownDiagnosticDirsUnix
226
+ const possibleDiagsPaths = expandDiagnosticDirCandidates([
227
+ ...getGithubDiagnosticDirsFromEnv(runnerTemp), ...patterns, ...literals,
228
+ ])
142
229
 
143
230
  // This will hold the names of the worker log files that (potentially) contain the Job ID
144
231
  let workerLogFiles = []
@@ -177,7 +264,7 @@ function getJobIDFromDiagFile (runnerTemp) {
177
264
  const filePath = path.posix.join(chosenDiagPath, logFile)
178
265
  const content = readFileSync(filePath, 'utf8')
179
266
 
180
- const match = content.match(/"job":\s*{[\s\S]*?"v"\s*:\s*(\d+)(?:\.0)?/)
267
+ const match = content.match(githubJobIDRegex)
181
268
 
182
269
  // match[1] is the captured group with the display name
183
270
  if (match && match[1]) { return match[1] }
@@ -188,6 +275,7 @@ function getJobIDFromDiagFile (runnerTemp) {
188
275
 
189
276
  module.exports = {
190
277
  normalizeRef,
278
+ expandGlobPattern,
191
279
  getJobIDFromDiagFile,
192
280
  getCIMetadata () {
193
281
  const env = getEnvironmentVariables()
@@ -366,7 +454,6 @@ module.exports = {
366
454
  GITHUB_RUN_ATTEMPT,
367
455
  GITHUB_JOB,
368
456
  GITHUB_BASE_REF,
369
- RUNNER_TEMP,
370
457
  JOB_CHECK_RUN_ID,
371
458
  } = env
372
459
 
@@ -378,7 +465,7 @@ module.exports = {
378
465
  }
379
466
 
380
467
  // Build the job url extracting the job ID. If extraction fails, job url is constructed as a generalized url
381
- const GITHUB_JOB_ID = JOB_CHECK_RUN_ID ?? getJobIDFromDiagFile(RUNNER_TEMP)
468
+ const GITHUB_JOB_ID = JOB_CHECK_RUN_ID ?? getJobIDFromDiagFile()
382
469
  const jobUrl =
383
470
  GITHUB_JOB_ID === null
384
471
  ? `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks`