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
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 (
|
|
37
|
-
if (this.
|
|
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
|
-
|
|
70
|
+
this.#map.set(key, value)
|
|
71
|
+
return this
|
|
42
72
|
}
|
|
43
73
|
|
|
44
|
-
|
|
74
|
+
get (key) {
|
|
75
|
+
return this.#map.get(key)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
delete (key) {
|
|
45
79
|
this.changed = true
|
|
46
|
-
return
|
|
80
|
+
return this.#map.delete(key)
|
|
47
81
|
}
|
|
48
82
|
|
|
49
|
-
clear (
|
|
83
|
+
clear () {
|
|
50
84
|
this.changed = true
|
|
51
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
get size () {
|
|
140
|
+
return this.#map.size
|
|
75
141
|
}
|
|
76
142
|
|
|
77
143
|
forVendor (vendor, handle) {
|
|
78
|
-
const data =
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,8 @@ class DatadogSpan {
|
|
|
336
344
|
let startTime
|
|
337
345
|
|
|
338
346
|
let baggage = {}
|
|
339
|
-
|
|
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 (
|
|
387
|
+
if (propagationBehavior === 'restart') {
|
|
379
388
|
spanContext._baggageItems = baggage
|
|
380
389
|
}
|
|
381
390
|
}
|
|
@@ -103,42 +103,129 @@ function getGitHubEventPayload () {
|
|
|
103
103
|
return JSON.parse(readFileSync(path, 'utf8'))
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
115
|
-
|
|
138
|
+
function hasMagicChars (str) {
|
|
139
|
+
return str.includes('*') || str.includes('?')
|
|
140
|
+
}
|
|
116
141
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
149
|
+
const prefix = parts.slice(0, wildcardIdx).join('/')
|
|
150
|
+
const results = []
|
|
125
151
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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(
|
|
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(
|
|
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`
|