dd-trace 5.106.0 → 5.107.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 +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber.js +7 -0
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +1 -2
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -5,6 +5,7 @@ const { stableStringify } = require('../otlp/otlp_transformer_base')
|
|
|
5
5
|
const {
|
|
6
6
|
METRIC_TYPES, TEMPORALITY, DEFAULT_HISTOGRAM_BUCKETS, DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE,
|
|
7
7
|
} = require('./constants')
|
|
8
|
+
const { ObservableInstrument } = require('./instruments')
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
@@ -102,6 +103,7 @@ class PeriodicMetricReader {
|
|
|
102
103
|
#isShutdown = false
|
|
103
104
|
#exportInterval
|
|
104
105
|
#aggregator
|
|
106
|
+
#batchCallbacks = []
|
|
105
107
|
|
|
106
108
|
/**
|
|
107
109
|
* Creates a new PeriodicMetricReader instance.
|
|
@@ -132,6 +134,66 @@ class PeriodicMetricReader {
|
|
|
132
134
|
this.#measurements.push(measurement)
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Registers a batch observable callback. Mirrors
|
|
139
|
+
* `@opentelemetry/sdk-metrics` `ObservableRegistry.addBatchCallback`.
|
|
140
|
+
*
|
|
141
|
+
* @param {Function} callback
|
|
142
|
+
* @param {Array} observables
|
|
143
|
+
*/
|
|
144
|
+
addBatchObservableCallback (callback, observables) {
|
|
145
|
+
if (typeof callback !== 'function') return
|
|
146
|
+
const instruments = new Set(observables?.filter(isObservableInstrument))
|
|
147
|
+
if (instruments.size === 0) return
|
|
148
|
+
if (this.#findBatchCallback(callback, instruments) !== -1) return
|
|
149
|
+
this.#batchCallbacks.push({ callback, instruments })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Function} callback
|
|
154
|
+
* @param {Array} observables
|
|
155
|
+
*/
|
|
156
|
+
removeBatchObservableCallback (callback, observables) {
|
|
157
|
+
const instruments = new Set(observables?.filter(isObservableInstrument))
|
|
158
|
+
const idx = this.#findBatchCallback(callback, instruments)
|
|
159
|
+
if (idx !== -1) this.#batchCallbacks.splice(idx, 1)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {Function} callback
|
|
164
|
+
* @param {Set} instruments
|
|
165
|
+
* @returns {number} index in #batchCallbacks, or -1
|
|
166
|
+
*/
|
|
167
|
+
#findBatchCallback (callback, instruments) {
|
|
168
|
+
return this.#batchCallbacks.findIndex(record =>
|
|
169
|
+
record.callback === callback && setEquals(record.instruments, instruments))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Invokes batch observable callbacks and returns the produced measurements.
|
|
174
|
+
*
|
|
175
|
+
* @returns {Measurement[]}
|
|
176
|
+
*/
|
|
177
|
+
#collectBatchObservables () {
|
|
178
|
+
if (this.#batchCallbacks.length === 0) return []
|
|
179
|
+
const out = []
|
|
180
|
+
for (const { callback, instruments } of this.#batchCallbacks) {
|
|
181
|
+
const result = {
|
|
182
|
+
observe: (instrument, value, attributes) => {
|
|
183
|
+
if (instruments.has(instrument)) {
|
|
184
|
+
out.push(instrument.createObservation(value, attributes))
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
callback(result)
|
|
190
|
+
} catch (e) {
|
|
191
|
+
log.error('Error running batch observable callback', e)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out
|
|
195
|
+
}
|
|
196
|
+
|
|
135
197
|
/**
|
|
136
198
|
* Forces an immediate collection and export of all metrics.
|
|
137
199
|
* @returns {void}
|
|
@@ -210,6 +272,17 @@ class PeriodicMetricReader {
|
|
|
210
272
|
}
|
|
211
273
|
}
|
|
212
274
|
|
|
275
|
+
const batchMeasurements = this.#collectBatchObservables()
|
|
276
|
+
if (batchMeasurements.length > 0) {
|
|
277
|
+
const remainingCapacity = DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE - allMeasurements.length
|
|
278
|
+
if (batchMeasurements.length <= remainingCapacity) {
|
|
279
|
+
allMeasurements.push(...batchMeasurements)
|
|
280
|
+
} else {
|
|
281
|
+
allMeasurements.push(...batchMeasurements.slice(0, remainingCapacity))
|
|
282
|
+
this.#droppedCount += batchMeasurements.length - remainingCapacity
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
213
286
|
if (this.#droppedCount > 0) {
|
|
214
287
|
log.warn('Metric queue exceeded limit (max: %d). Dropping %d measurements.',
|
|
215
288
|
DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE, this.#droppedCount)
|
|
@@ -555,4 +628,23 @@ class MetricAggregator {
|
|
|
555
628
|
}
|
|
556
629
|
}
|
|
557
630
|
|
|
631
|
+
/**
|
|
632
|
+
* @param {object} x
|
|
633
|
+
* @returns {boolean}
|
|
634
|
+
*/
|
|
635
|
+
function isObservableInstrument (x) {
|
|
636
|
+
return x instanceof ObservableInstrument
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* @param {Set} a
|
|
641
|
+
* @param {Set} b
|
|
642
|
+
* @returns {boolean}
|
|
643
|
+
*/
|
|
644
|
+
function setEquals (a, b) {
|
|
645
|
+
if (a.size !== b.size) return false
|
|
646
|
+
for (const x of a) if (!b.has(x)) return false
|
|
647
|
+
return true
|
|
648
|
+
}
|
|
649
|
+
|
|
558
650
|
module.exports = PeriodicMetricReader
|
|
@@ -4,6 +4,7 @@ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
|
|
|
4
4
|
const { getProtobufTypes } = require('../otlp/protobuf_loader')
|
|
5
5
|
const { VERSION } = require('../../../../../version')
|
|
6
6
|
const id = require('../../id')
|
|
7
|
+
const { eventTimeNano } = require('../../encode/tags-processors')
|
|
7
8
|
|
|
8
9
|
const { protoSpanKind } = getProtobufTypes()
|
|
9
10
|
const SPAN_KIND_UNSPECIFIED = protoSpanKind.values.SPAN_KIND_UNSPECIFIED
|
|
@@ -33,7 +34,7 @@ const TRACE_ID_128 = '_dd.p.tid'
|
|
|
33
34
|
*
|
|
34
35
|
* @typedef {object} DDSpanEvent
|
|
35
36
|
* @property {string} name - Event name
|
|
36
|
-
* @property {number}
|
|
37
|
+
* @property {number} startTime - Event start time in milliseconds (sub-ms precision)
|
|
37
38
|
* @property {Record<string, string | number | boolean>} [attributes] - Event attributes
|
|
38
39
|
*
|
|
39
40
|
* @typedef {object} DDFormattedSpan
|
|
@@ -274,7 +275,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
274
275
|
*/
|
|
275
276
|
#transformEvent (event) {
|
|
276
277
|
return {
|
|
277
|
-
timeUnixNano: event
|
|
278
|
+
timeUnixNano: eventTimeNano(event),
|
|
278
279
|
name: event.name || '',
|
|
279
280
|
attributes: this.transformAttributes(event.attributes ?? {}),
|
|
280
281
|
droppedAttributesCount: 0,
|
|
@@ -38,20 +38,26 @@ const tagsUpdateCh = channel('dd-trace:span:tags:update')
|
|
|
38
38
|
// Module-scope so we don't allocate a fresh recursive closure on every
|
|
39
39
|
// `addLink` / `addEvent`.
|
|
40
40
|
/**
|
|
41
|
-
* @param {Record<string, string>} out
|
|
41
|
+
* @param {Record<string, string> | undefined} out Accumulator, created lazily
|
|
42
|
+
* on the first surviving entry so an all-dropped set stays `undefined`.
|
|
42
43
|
* @param {string} key
|
|
43
44
|
* @param {unknown} value
|
|
45
|
+
* @returns {Record<string, string> | undefined}
|
|
44
46
|
*/
|
|
45
47
|
function addArrayOrScalarAttribute (out, key, value) {
|
|
46
48
|
if (Array.isArray(value)) {
|
|
47
49
|
for (let i = 0; i < value.length; i++) {
|
|
48
|
-
addArrayOrScalarAttribute(out, `${key}.${i}`, value[i])
|
|
50
|
+
out = addArrayOrScalarAttribute(out, `${key}.${i}`, value[i])
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
return out
|
|
53
|
+
}
|
|
54
|
+
if (ALLOWED.has(typeof value)) {
|
|
55
|
+
out ??= {}
|
|
51
56
|
out[key] = typeof value === 'string' ? value : String(value)
|
|
52
|
-
|
|
53
|
-
log.warn('Dropping span link attribute. It is not of an allowed type')
|
|
57
|
+
return out
|
|
54
58
|
}
|
|
59
|
+
log.warn('Dropping span link attribute. It is not of an allowed type')
|
|
60
|
+
return out
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
function getIntegrationCounter (event, integration) {
|
|
@@ -79,12 +85,6 @@ class DatadogSpan {
|
|
|
79
85
|
|
|
80
86
|
const operationName = fields.operationName
|
|
81
87
|
const parent = fields.parent || null
|
|
82
|
-
// Stay on `Object.assign({}, src)` for backportability: V8 12+ (Node 22 /
|
|
83
|
-
// 24) inlines `{ ...src }` and beats `Object.assign` here, but on V8 10.2
|
|
84
|
-
// / 11.3 (Node 18 / 20) the spread takes a generic runtime path and slows
|
|
85
|
-
// `spans-finish-*` by ~140%. Revisit once those LTS lines drop.
|
|
86
|
-
// eslint-disable-next-line prefer-object-spread
|
|
87
|
-
const tags = Object.assign({}, fields.tags)
|
|
88
88
|
const hostname = fields.hostname
|
|
89
89
|
|
|
90
90
|
this.#parentTracer = tracer
|
|
@@ -106,7 +106,7 @@ class DatadogSpan {
|
|
|
106
106
|
|
|
107
107
|
this._spanContext = this._createContext(parent, fields)
|
|
108
108
|
this._spanContext._name = operationName
|
|
109
|
-
Object.assign(this._spanContext.getTags(), tags)
|
|
109
|
+
if (fields.tags) Object.assign(this._spanContext.getTags(), fields.tags)
|
|
110
110
|
this._spanContext._hostname = hostname
|
|
111
111
|
|
|
112
112
|
this._spanContext._trace.started.push(this)
|
|
@@ -348,21 +348,24 @@ class DatadogSpan {
|
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
350
|
* @param {Record<string, unknown>} [attributes]
|
|
351
|
+
* @returns {Record<string, string> | undefined} `undefined` when nothing
|
|
352
|
+
* survives, so `extractSpanLinks` omits the slot without an emptiness probe.
|
|
351
353
|
*/
|
|
352
354
|
_sanitizeAttributes (attributes = {}) {
|
|
353
|
-
|
|
354
|
-
const out = {}
|
|
355
|
+
let out
|
|
355
356
|
for (const key of Object.keys(attributes)) {
|
|
356
|
-
addArrayOrScalarAttribute(out, key, attributes[key])
|
|
357
|
+
out = addArrayOrScalarAttribute(out, key, attributes[key])
|
|
357
358
|
}
|
|
358
359
|
return out
|
|
359
360
|
}
|
|
360
361
|
|
|
361
362
|
/**
|
|
362
363
|
* @param {Record<string, unknown>} [attributes]
|
|
364
|
+
* @returns {Record<string, unknown> | undefined} `undefined` when nothing
|
|
365
|
+
* survives, so the encoders skip the slot without an emptiness probe.
|
|
363
366
|
*/
|
|
364
367
|
_sanitizeEventAttributes (attributes = {}) {
|
|
365
|
-
|
|
368
|
+
let sanitizedAttributes
|
|
366
369
|
|
|
367
370
|
for (const key of Object.keys(attributes)) {
|
|
368
371
|
const value = attributes[key]
|
|
@@ -375,8 +378,10 @@ class DatadogSpan {
|
|
|
375
378
|
log.warn('Dropping span event attribute. It is not of an allowed type')
|
|
376
379
|
}
|
|
377
380
|
}
|
|
381
|
+
sanitizedAttributes ??= {}
|
|
378
382
|
sanitizedAttributes[key] = newArray
|
|
379
383
|
} else if (ALLOWED.has(typeof value)) {
|
|
384
|
+
sanitizedAttributes ??= {}
|
|
380
385
|
sanitizedAttributes[key] = value
|
|
381
386
|
} else {
|
|
382
387
|
log.warn('Dropping span event attribute. It is not of an allowed type')
|
|
@@ -389,7 +394,7 @@ class DatadogSpan {
|
|
|
389
394
|
let spanContext
|
|
390
395
|
let startTime
|
|
391
396
|
|
|
392
|
-
let baggage
|
|
397
|
+
let baggage
|
|
393
398
|
const propagationBehavior = this.#parentTracer._config.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT
|
|
394
399
|
if (parent && parent._isRemote && propagationBehavior !== 'continue') {
|
|
395
400
|
baggage = parent._baggageItems
|
|
@@ -431,7 +436,7 @@ class DatadogSpan {
|
|
|
431
436
|
}
|
|
432
437
|
|
|
433
438
|
if (propagationBehavior === 'restart') {
|
|
434
|
-
spanContext._baggageItems = baggage
|
|
439
|
+
spanContext._baggageItems = baggage ?? {}
|
|
435
440
|
}
|
|
436
441
|
}
|
|
437
442
|
|
|
@@ -23,6 +23,8 @@ class DatadogTracer {
|
|
|
23
23
|
constructor (config, prioritySampler) {
|
|
24
24
|
this._config = config
|
|
25
25
|
this._service = config.service
|
|
26
|
+
// Lowercased once for span_format's per-span base-service comparison.
|
|
27
|
+
this.serviceLower = typeof config.service === 'string' ? config.service.toLowerCase() : ''
|
|
26
28
|
this._version = config.version
|
|
27
29
|
this._env = config.env
|
|
28
30
|
this._logInjection = config.logInjection
|
|
@@ -64,21 +66,9 @@ class DatadogTracer {
|
|
|
64
66
|
? getContext(options.childOf)
|
|
65
67
|
: getParent(options.references)
|
|
66
68
|
|
|
67
|
-
// as per spec, allow the setting of service name through options
|
|
68
|
-
const tags = {
|
|
69
|
-
'service.name': options?.tags?.service ? String(options.tags.service) : this._service,
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// As per unified service tagging spec if a span is created with a service name different from the global
|
|
73
|
-
// service name it will not inherit the global version value
|
|
74
|
-
if (options?.tags?.service && options.tags.service !== this._service) {
|
|
75
|
-
options.tags.version = undefined
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
const span = new Span(this, this._processor, this._prioritySampler, {
|
|
79
70
|
operationName: options.operationName || name,
|
|
80
71
|
parent,
|
|
81
|
-
tags,
|
|
82
72
|
startTime: options.startTime,
|
|
83
73
|
hostname: this._hostname,
|
|
84
74
|
traceId128BitGenerationEnabled: this._traceId128BitGenerationEnabled,
|
|
@@ -86,6 +76,20 @@ class DatadogTracer {
|
|
|
86
76
|
links: options.links,
|
|
87
77
|
}, this._debug)
|
|
88
78
|
|
|
79
|
+
// As per unified service tagging spec if a span is created with a service name different from the global
|
|
80
|
+
// service name it will not inherit the global version value
|
|
81
|
+
const ctx = span.context()
|
|
82
|
+
if (options.tags?.service) {
|
|
83
|
+
if (options.tags.service !== this._service) options.tags.version = undefined
|
|
84
|
+
// as per spec, allow the setting of service name through options; set it
|
|
85
|
+
// after all tags are merged so config/options values take precedence
|
|
86
|
+
// eslint-disable-next-line eslint-rules/eslint-prefer-set-service-name
|
|
87
|
+
ctx.setTag('service.name', String(options.tags.service))
|
|
88
|
+
} else {
|
|
89
|
+
// eslint-disable-next-line eslint-rules/eslint-prefer-set-service-name
|
|
90
|
+
ctx.setTag('service.name', this._service)
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
span.addTags(this._config.tags)
|
|
90
94
|
span.addTags(options.tags)
|
|
91
95
|
|
|
@@ -143,6 +143,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
143
143
|
this.fileLineToProbeId = new Map()
|
|
144
144
|
this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
|
|
145
145
|
this._testSuiteSpansByTestSuite = new Map()
|
|
146
|
+
this._pendingWorkerTracesByTestSuite = new Map()
|
|
146
147
|
this._pendingRequestErrorTags = []
|
|
147
148
|
|
|
148
149
|
this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
|
|
@@ -352,52 +353,8 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
352
353
|
const formattedTraces = JSON.parse(traces)
|
|
353
354
|
|
|
354
355
|
for (const trace of formattedTraces) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
span.trace_id = id(span.trace_id)
|
|
358
|
-
span.parent_id = id(span.parent_id)
|
|
359
|
-
|
|
360
|
-
if (span.name?.startsWith(`${this.constructor.id}.`)) {
|
|
361
|
-
span.meta[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
|
|
362
|
-
if (span.name === `${this.constructor.id}.test` || span.name === `${this.constructor.id}.test_suite`) {
|
|
363
|
-
Object.assign(span.meta, getSessionItrSkippingEnabledTags(this.testSessionSpan))
|
|
364
|
-
}
|
|
365
|
-
// augment with git information (since it will not be available in the worker)
|
|
366
|
-
for (const key in this.testEnvironmentMetadata) {
|
|
367
|
-
// CAREFUL: this bypasses the metadata/metrics distinction
|
|
368
|
-
// Be careful not to pass numbers in `meta`
|
|
369
|
-
if (key.startsWith('git.')) {
|
|
370
|
-
span.meta[key] = this.testEnvironmentMetadata[key]
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Only test hooks run in the cucumber worker, so the test events do not have the
|
|
376
|
-
// test session, test module and test suite ids. We have to update them here.
|
|
377
|
-
if (span.name === 'cucumber.test' || span.name === 'mocha.test') {
|
|
378
|
-
const testSuite = span.meta[TEST_SUITE]
|
|
379
|
-
const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
|
|
380
|
-
if (!testSuiteSpan) {
|
|
381
|
-
log.warn('Test suite span not found for test span with test suite %s', testSuite)
|
|
382
|
-
continue
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
|
|
386
|
-
span.meta = {
|
|
387
|
-
...span.meta,
|
|
388
|
-
...testSuiteTags,
|
|
389
|
-
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Jest and Vitest worker test spans are serialized in the worker and may not include
|
|
394
|
-
// request error tags; add them from the session span in the main process.
|
|
395
|
-
if ((span.name === 'jest.test' || span.name === 'vitest.test' || span.name === 'vitest.test_suite') &&
|
|
396
|
-
this.testSessionSpan) {
|
|
397
|
-
Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
this.tracer._exporter.export(trace)
|
|
356
|
+
this._prepareWorkerTrace(trace)
|
|
357
|
+
this._exportWorkerTraceOrBuffer(trace)
|
|
401
358
|
}
|
|
402
359
|
})
|
|
403
360
|
|
|
@@ -502,6 +459,134 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
502
459
|
return getSessionItrSkippingEnabledTags(this.testSessionSpan)
|
|
503
460
|
}
|
|
504
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Normalizes worker trace identifiers and adds process-level metadata before exporting or buffering.
|
|
464
|
+
*
|
|
465
|
+
* @param {object[]} trace - Worker trace spans decoded from the worker payload.
|
|
466
|
+
*/
|
|
467
|
+
_prepareWorkerTrace (trace) {
|
|
468
|
+
for (const span of trace) {
|
|
469
|
+
span.span_id = id(span.span_id)
|
|
470
|
+
span.trace_id = id(span.trace_id)
|
|
471
|
+
span.parent_id = id(span.parent_id)
|
|
472
|
+
|
|
473
|
+
if (span.name?.startsWith(`${this.constructor.id}.`)) {
|
|
474
|
+
span.meta[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
|
|
475
|
+
if (span.name === `${this.constructor.id}.test` || span.name === `${this.constructor.id}.test_suite`) {
|
|
476
|
+
Object.assign(span.meta, getSessionItrSkippingEnabledTags(this.testSessionSpan))
|
|
477
|
+
}
|
|
478
|
+
// augment with git information (since it will not be available in the worker)
|
|
479
|
+
for (const key in this.testEnvironmentMetadata) {
|
|
480
|
+
// CAREFUL: this bypasses the metadata/metrics distinction
|
|
481
|
+
// Be careful not to pass numbers in `meta`
|
|
482
|
+
if (key.startsWith('git.')) {
|
|
483
|
+
span.meta[key] = this.testEnvironmentMetadata[key]
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Jest and Vitest worker test spans are serialized in the worker and may not include
|
|
489
|
+
// request error tags; add them from the session span in the main process.
|
|
490
|
+
if ((span.name === 'jest.test' || span.name === 'vitest.test' || span.name === 'vitest.test_suite') &&
|
|
491
|
+
this.testSessionSpan) {
|
|
492
|
+
Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Adds suite-level CI Visibility tags to worker test spans when their suite span is available.
|
|
499
|
+
*
|
|
500
|
+
* @param {object[]} trace - Worker trace spans.
|
|
501
|
+
* @returns {string|undefined} Missing test suite name, if the trace cannot be exported yet.
|
|
502
|
+
*/
|
|
503
|
+
_addSuiteTagsToWorkerTrace (trace) {
|
|
504
|
+
for (const span of trace) {
|
|
505
|
+
// Only test hooks run in Cucumber and Mocha workers, so the test events do not have the
|
|
506
|
+
// test session, test module and test suite ids. We have to update them in the main process.
|
|
507
|
+
if (span.name !== 'cucumber.test' && span.name !== 'mocha.test') continue
|
|
508
|
+
|
|
509
|
+
const testSuite = span.meta[TEST_SUITE]
|
|
510
|
+
const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
|
|
511
|
+
if (!testSuiteSpan) return testSuite
|
|
512
|
+
|
|
513
|
+
const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
|
|
514
|
+
span.meta = {
|
|
515
|
+
...span.meta,
|
|
516
|
+
...testSuiteTags,
|
|
517
|
+
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Stores a worker trace until the matching test suite span exists in the main process.
|
|
524
|
+
*
|
|
525
|
+
* @param {string} testSuite - Test suite path used as the pending trace key.
|
|
526
|
+
* @param {object[]} trace - Worker trace spans.
|
|
527
|
+
*/
|
|
528
|
+
_bufferWorkerTrace (testSuite, trace) {
|
|
529
|
+
let pendingTraces = this._pendingWorkerTracesByTestSuite.get(testSuite)
|
|
530
|
+
if (!pendingTraces) {
|
|
531
|
+
pendingTraces = []
|
|
532
|
+
this._pendingWorkerTracesByTestSuite.set(testSuite, pendingTraces)
|
|
533
|
+
}
|
|
534
|
+
pendingTraces.push(trace)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Exports a worker trace immediately, or buffers it if suite-level tags cannot be added yet.
|
|
539
|
+
*
|
|
540
|
+
* @param {object[]} trace - Worker trace spans.
|
|
541
|
+
*/
|
|
542
|
+
_exportWorkerTraceOrBuffer (trace) {
|
|
543
|
+
const missingTestSuite = this._addSuiteTagsToWorkerTrace(trace)
|
|
544
|
+
if (missingTestSuite) {
|
|
545
|
+
this._bufferWorkerTrace(missingTestSuite, trace)
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
this.tracer._exporter.export(trace)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Exports buffered worker traces for a suite after its suite span has been created.
|
|
553
|
+
*
|
|
554
|
+
* @param {string} testSuite - Test suite path that may now have pending worker traces.
|
|
555
|
+
*/
|
|
556
|
+
_exportPendingWorkerTracesForTestSuite (testSuite) {
|
|
557
|
+
const pendingTraces = this._pendingWorkerTracesByTestSuite.get(testSuite)
|
|
558
|
+
if (!pendingTraces) return
|
|
559
|
+
|
|
560
|
+
this._pendingWorkerTracesByTestSuite.delete(testSuite)
|
|
561
|
+
for (const trace of pendingTraces) {
|
|
562
|
+
this._exportWorkerTraceOrBuffer(trace)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Drains all buffered worker traces, falling back to the previous unaugmented export behavior
|
|
568
|
+
* if a matching suite span never appears.
|
|
569
|
+
*/
|
|
570
|
+
_exportPendingWorkerTraces () {
|
|
571
|
+
if (!this._pendingWorkerTracesByTestSuite.size) return
|
|
572
|
+
|
|
573
|
+
const pendingTraces = new Set()
|
|
574
|
+
for (const traces of this._pendingWorkerTracesByTestSuite.values()) {
|
|
575
|
+
for (const trace of traces) {
|
|
576
|
+
pendingTraces.add(trace)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
this._pendingWorkerTracesByTestSuite.clear()
|
|
580
|
+
|
|
581
|
+
for (const trace of pendingTraces) {
|
|
582
|
+
const missingTestSuite = this._addSuiteTagsToWorkerTrace(trace)
|
|
583
|
+
if (missingTestSuite) {
|
|
584
|
+
log.warn('Test suite span not found for test span with test suite %s', missingTestSuite)
|
|
585
|
+
}
|
|
586
|
+
this.tracer._exporter.export(trace)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
505
590
|
/**
|
|
506
591
|
* @param {import('../config/config-base')} config - Tracer configuration
|
|
507
592
|
* @param {boolean} shouldGetEnvironmentData - Whether to get environment data
|
|
@@ -333,11 +333,12 @@ class PrioritySampler {
|
|
|
333
333
|
if (!trace.tags[DECISION_MAKER_KEY]) {
|
|
334
334
|
trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
|
|
335
335
|
}
|
|
336
|
-
} else if (DECISION_MAKER_KEY
|
|
337
|
-
//
|
|
338
|
-
// dictionary
|
|
339
|
-
//
|
|
340
|
-
|
|
336
|
+
} else if (trace.tags[DECISION_MAKER_KEY] !== undefined) {
|
|
337
|
+
// Clear by assigning undefined rather than deleting: `delete` drops
|
|
338
|
+
// trace.tags into V8 dictionary (slow) mode for the per-trace extract
|
|
339
|
+
// and propagation scans that follow. Both skip undefined values, so the
|
|
340
|
+
// emitted meta and injected headers are unchanged.
|
|
341
|
+
trace.tags[DECISION_MAKER_KEY] = undefined
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
|
|
@@ -10,7 +10,6 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
|
10
10
|
const { getIsAzureFunction } = require('../serverless')
|
|
11
11
|
const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
|
|
12
12
|
const { getEnvironmentVariable } = require('../config/helper')
|
|
13
|
-
const { getAgentUrl } = require('../agent/url')
|
|
14
13
|
const { isACFActive } = require('../../../datadog-core/src/storage')
|
|
15
14
|
|
|
16
15
|
const { AgentExporter } = require('./exporters/agent')
|
|
@@ -54,7 +53,7 @@ class Config {
|
|
|
54
53
|
this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
|
|
55
54
|
this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
|
|
56
55
|
|
|
57
|
-
this.url =
|
|
56
|
+
this.url = options.url
|
|
58
57
|
|
|
59
58
|
this.libraryInjected = !!options.DD_INJECTION_ENABLED
|
|
60
59
|
|
|
@@ -195,6 +195,19 @@ class Tracer extends NoopProxy {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// OTel logs/metrics pipelines must be initialized BEFORE runtimeMetrics.start so that
|
|
199
|
+
// when the OTLP runtime metrics module calls metrics.getMeterProvider(), it gets the
|
|
200
|
+
// real provider, otherwise instruments register on the noop provider and never export.
|
|
201
|
+
if (config.DD_LOGS_OTEL_ENABLED) {
|
|
202
|
+
const { initializeOpenTelemetryLogs } = require('./opentelemetry/logs')
|
|
203
|
+
initializeOpenTelemetryLogs(config)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (config.DD_METRICS_OTEL_ENABLED) {
|
|
207
|
+
const { initializeOpenTelemetryMetrics } = require('./opentelemetry/metrics')
|
|
208
|
+
initializeOpenTelemetryMetrics(config)
|
|
209
|
+
}
|
|
210
|
+
|
|
198
211
|
if (config.runtimeMetrics.enabled) {
|
|
199
212
|
runtimeMetrics.start(config)
|
|
200
213
|
}
|
|
@@ -224,16 +237,6 @@ class Tracer extends NoopProxy {
|
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
if (config.DD_LOGS_OTEL_ENABLED) {
|
|
228
|
-
const { initializeOpenTelemetryLogs } = require('./opentelemetry/logs')
|
|
229
|
-
initializeOpenTelemetryLogs(config)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (config.DD_METRICS_OTEL_ENABLED) {
|
|
233
|
-
const { initializeOpenTelemetryMetrics } = require('./opentelemetry/metrics')
|
|
234
|
-
initializeOpenTelemetryMetrics(config)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
240
|
if (config.isTestDynamicInstrumentationEnabled) {
|
|
238
241
|
const getDynamicInstrumentationClient = require('./ci-visibility/dynamic-instrumentation')
|
|
239
242
|
// We instantiate the client but do not start the Worker here. The worker is started lazily
|
|
@@ -8,7 +8,6 @@ const { getExtraServices } = require('../service-naming/extra-services')
|
|
|
8
8
|
const getGitMetadata = require('../git_metadata')
|
|
9
9
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
10
10
|
const tagger = require('../tagger')
|
|
11
|
-
const { getAgentUrl } = require('../agent/url')
|
|
12
11
|
const processTags = require('../process-tags')
|
|
13
12
|
const Scheduler = require('./scheduler')
|
|
14
13
|
const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
|
|
@@ -32,7 +31,7 @@ class RemoteConfig {
|
|
|
32
31
|
constructor (config) {
|
|
33
32
|
const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
|
|
34
33
|
|
|
35
|
-
this.url =
|
|
34
|
+
this.url = config.url
|
|
36
35
|
|
|
37
36
|
tagger.add(config.tags, {
|
|
38
37
|
'_dd.rc.client_id': clientId,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { DogStatsDClient, MetricsAggregationClient } = require('../dogstatsd')
|
|
4
|
+
const processTags = require('../process-tags')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds the aggregating DogStatsD client used to emit DD-proprietary tracer
|
|
8
|
+
* metrics (runtime.node.*, datadog.tracer.*). Shared by both runtime-metrics
|
|
9
|
+
* paths (DogStatsD and OTLP) so their client construction can't drift apart.
|
|
10
|
+
*
|
|
11
|
+
* Process tags are applied here, not via config/generateClientConfig, so they only
|
|
12
|
+
* reach this bounded set of runtime metrics. Putting them on the global tags would
|
|
13
|
+
* also tag user-facing custom metrics, inflating their cardinality (and billing).
|
|
14
|
+
*
|
|
15
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
16
|
+
* @returns {MetricsAggregationClient}
|
|
17
|
+
*/
|
|
18
|
+
function createMetricsClient (config) {
|
|
19
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
20
|
+
|
|
21
|
+
if (config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) {
|
|
22
|
+
for (const tag of processTags.tagsArray) {
|
|
23
|
+
clientConfig.tags.push(tag)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return new MetricsAggregationClient(new DogStatsDClient(clientConfig))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { createMetricsClient }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
3
5
|
let runtimeMetrics
|
|
4
6
|
|
|
5
7
|
const noop = runtimeMetrics = {
|
|
@@ -20,11 +22,19 @@ module.exports = {
|
|
|
20
22
|
start (config) {
|
|
21
23
|
if (!config?.runtimeMetrics.enabled) return
|
|
22
24
|
|
|
23
|
-
runtimeMetrics =
|
|
25
|
+
runtimeMetrics = config.DD_METRICS_OTEL_ENABLED
|
|
26
|
+
? require('./otlp_runtime_metrics')
|
|
27
|
+
: require('./runtime_metrics')
|
|
24
28
|
|
|
25
29
|
Object.setPrototypeOf(module.exports, runtimeMetrics)
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
try {
|
|
32
|
+
runtimeMetrics.start(config)
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Unwind whatever managed to register so a partial init doesn't leak into the next start().
|
|
35
|
+
runtimeMetrics.stop()
|
|
36
|
+
log.error('Failed to start runtime metrics', err)
|
|
37
|
+
}
|
|
28
38
|
},
|
|
29
39
|
|
|
30
40
|
stop () {
|