dd-trace 5.87.0 → 5.88.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 +60 -32
- package/ext/exporters.d.ts +1 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +225 -4
- package/package.json +9 -6
- package/packages/datadog-instrumentations/src/ai.js +54 -90
- package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +55 -14
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +21 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +138 -12
- package/packages/datadog-instrumentations/src/jest.js +76 -12
- package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
- package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
- package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
- package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
- package/packages/datadog-plugin-cucumber/src/index.js +9 -6
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +26 -0
- package/packages/datadog-plugin-cypress/src/support.js +48 -8
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
- package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
- package/packages/datadog-plugin-mocha/src/index.js +9 -6
- package/packages/datadog-plugin-playwright/src/index.js +10 -6
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
- package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
- package/packages/dd-trace/src/azure_metadata.js +0 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +148 -197
- package/packages/dd-trace/src/config/helper.js +43 -1
- package/packages/dd-trace/src/config/index.js +36 -14
- package/packages/dd-trace/src/config/supported-configurations.json +4115 -512
- package/packages/dd-trace/src/constants.js +0 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
- package/packages/dd-trace/src/datastreams/pathway.js +22 -3
- package/packages/dd-trace/src/datastreams/processor.js +14 -1
- package/packages/dd-trace/src/encode/agentless-json.js +141 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/agent/writer.js +22 -8
- package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
- package/packages/dd-trace/src/exporters/common/request.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
- package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
- package/packages/dd-trace/src/opentracing/span.js +6 -4
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
- package/packages/dd-trace/src/plugins/database.js +15 -2
- package/packages/dd-trace/src/plugins/util/test.js +48 -0
- package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
- package/packages/dd-trace/src/propagation-hash/index.js +145 -0
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
- package/packages/dd-trace/src/scope/noop/scope.js +0 -21
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
|
|
4
4
|
const { storage } = require('../../../datadog-core')
|
|
5
|
+
const { getAllBaggageItems, setBaggageItem, removeAllBaggageItems } = require('../baggage')
|
|
5
6
|
|
|
6
7
|
const tracer = require('../../')
|
|
7
8
|
const SpanContext = require('./span_context')
|
|
@@ -19,31 +20,26 @@ class ContextManager {
|
|
|
19
20
|
|
|
20
21
|
const storedSpan = store ? trace.getSpan(store) : null
|
|
21
22
|
|
|
23
|
+
// Convert DD baggage to OTel format
|
|
24
|
+
const baggages = getAllBaggageItems()
|
|
25
|
+
const hasBaggage = Object.keys(baggages).length > 0
|
|
26
|
+
let otelBaggages
|
|
27
|
+
if (hasBaggage) {
|
|
28
|
+
const entries = {}
|
|
29
|
+
for (const [key, value] of Object.entries(baggages)) {
|
|
30
|
+
entries[key] = { value }
|
|
31
|
+
}
|
|
32
|
+
otelBaggages = propagation.createBaggage(entries)
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
// If stored span wraps the active DD span, prefer the stored context
|
|
23
36
|
if (storedSpan && storedSpan._ddSpan === activeSpan) {
|
|
24
|
-
|
|
25
|
-
if (Object.keys(baggages).length > 0) {
|
|
26
|
-
const entries = {}
|
|
27
|
-
for (const [key, value] of Object.entries(baggages)) {
|
|
28
|
-
entries[key] = { value }
|
|
29
|
-
}
|
|
30
|
-
const otelBaggages = propagation.createBaggage(entries)
|
|
31
|
-
return propagation.setBaggage(store, otelBaggages)
|
|
32
|
-
}
|
|
37
|
+
if (otelBaggages) return propagation.setBaggage(store, otelBaggages)
|
|
33
38
|
return store
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
if (!activeSpan) {
|
|
37
|
-
|
|
38
|
-
if (storedBaggageItems) {
|
|
39
|
-
const baggages = storedBaggageItems
|
|
40
|
-
const entries = {}
|
|
41
|
-
for (const [key, value] of Object.entries(baggages)) {
|
|
42
|
-
entries[key] = { value }
|
|
43
|
-
}
|
|
44
|
-
const otelBaggages = propagation.createBaggage(entries)
|
|
45
|
-
return propagation.setBaggage(baseContext, otelBaggages)
|
|
46
|
-
}
|
|
42
|
+
if (otelBaggages) return propagation.setBaggage(baseContext, otelBaggages)
|
|
47
43
|
return baseContext
|
|
48
44
|
}
|
|
49
45
|
|
|
@@ -53,18 +49,6 @@ class ContextManager {
|
|
|
53
49
|
ddContext._otelSpanContext = new SpanContext(ddContext)
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
// Convert DD baggage to OTel format
|
|
57
|
-
const baggages = JSON.parse(activeSpan.getAllBaggageItems())
|
|
58
|
-
const hasBaggage = Object.keys(baggages).length > 0
|
|
59
|
-
let otelBaggages
|
|
60
|
-
if (hasBaggage) {
|
|
61
|
-
const entries = {}
|
|
62
|
-
for (const [key, value] of Object.entries(baggages)) {
|
|
63
|
-
entries[key] = { value }
|
|
64
|
-
}
|
|
65
|
-
otelBaggages = propagation.createBaggage(entries)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
52
|
if (store && trace.getSpanContext(store) === ddContext._otelSpanContext) {
|
|
69
53
|
return otelBaggages ? propagation.setBaggage(store, otelBaggages) : store
|
|
70
54
|
}
|
|
@@ -86,22 +70,11 @@ class ContextManager {
|
|
|
86
70
|
if (baggages) {
|
|
87
71
|
baggageItems = baggages.getAllEntries()
|
|
88
72
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for (const baggage of baggageItems) {
|
|
93
|
-
span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
|
|
94
|
-
}
|
|
95
|
-
return ddScope.activate(span._ddSpan, run)
|
|
96
|
-
}
|
|
97
|
-
// span instanceof NonRecordingSpan
|
|
98
|
-
const ddContext = span?._spanContext?._ddContext
|
|
99
|
-
if (ddContext && ddContext._baggageItems) {
|
|
100
|
-
ddContext._baggageItems = {}
|
|
101
|
-
for (const baggage of baggageItems) {
|
|
102
|
-
ddContext._baggageItems[baggage[0]] = baggage[1].value
|
|
103
|
-
}
|
|
73
|
+
removeAllBaggageItems()
|
|
74
|
+
for (const baggage of baggageItems) {
|
|
75
|
+
setBaggageItem(baggage[0], baggage[1].value)
|
|
104
76
|
}
|
|
77
|
+
if (span && span._ddSpan) return ddScope.activate(span._ddSpan, run)
|
|
105
78
|
return run()
|
|
106
79
|
}
|
|
107
80
|
|
|
@@ -20,7 +20,7 @@ class OtlpHttpExporterBase {
|
|
|
20
20
|
* Creates a new OtlpHttpExporterBase instance.
|
|
21
21
|
*
|
|
22
22
|
* @param {string} url - OTLP endpoint URL
|
|
23
|
-
* @param {string} headers - Additional HTTP headers as comma-separated key=value string
|
|
23
|
+
* @param {string|undefined} headers - Additional HTTP headers as comma-separated key=value string
|
|
24
24
|
* @param {number} timeout - Request timeout in milliseconds
|
|
25
25
|
* @param {string} protocol - OTLP protocol (http/protobuf or http/json)
|
|
26
26
|
* @param {string} defaultPath - Default path to use if URL has no path
|
|
@@ -117,11 +117,10 @@ class OtlpHttpExporterBase {
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Parses additional HTTP headers from a comma-separated string.
|
|
120
|
-
* @param {string} headersString - Comma-separated key=value pairs
|
|
120
|
+
* @param {string} [headersString=''] - Comma-separated key=value pairs
|
|
121
121
|
* @returns {Record<string, string>} Parsed headers object
|
|
122
|
-
* @private
|
|
123
122
|
*/
|
|
124
|
-
#parseAdditionalHeaders (headersString) {
|
|
123
|
+
#parseAdditionalHeaders (headersString = '') {
|
|
125
124
|
const headers = {}
|
|
126
125
|
let key = ''
|
|
127
126
|
let value = ''
|
|
@@ -670,10 +670,8 @@ class TextMapPropagator {
|
|
|
670
670
|
if (!this._hasPropagationStyle('extract', 'baggage')) return
|
|
671
671
|
if (!carrier?.baggage) return
|
|
672
672
|
const baggages = carrier.baggage.split(',')
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
? undefined
|
|
676
|
-
: new Set(this._config.baggageTagKeys.split(','))
|
|
673
|
+
const baggageTagKeys = new Set(this._config.baggageTagKeys)
|
|
674
|
+
const tagAllKeys = baggageTagKeys.has('*')
|
|
677
675
|
for (const keyValue of baggages) {
|
|
678
676
|
if (!keyValue) continue
|
|
679
677
|
|
|
@@ -707,7 +705,7 @@ class TextMapPropagator {
|
|
|
707
705
|
return
|
|
708
706
|
}
|
|
709
707
|
|
|
710
|
-
if (spanContext && (tagAllKeys ||
|
|
708
|
+
if (spanContext && (tagAllKeys || baggageTagKeys.has(key))) {
|
|
711
709
|
spanContext._trace.tags['baggage.' + key] = value
|
|
712
710
|
}
|
|
713
711
|
setBaggageItem(key, value)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// TODO (new internal tracer): use DC events for lifecycle metrics and test them
|
|
4
4
|
const { performance } = require('perf_hooks')
|
|
5
5
|
const now = performance.now.bind(performance)
|
|
6
|
-
const dateNow = Date.now
|
|
7
6
|
const util = require('util')
|
|
8
7
|
const { channel } = require('dc-polyfill')
|
|
9
8
|
const id = require('../id')
|
|
@@ -13,12 +12,15 @@ const log = require('../log')
|
|
|
13
12
|
const { storage } = require('../../../datadog-core')
|
|
14
13
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
15
14
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
15
|
+
const { isTrue } = require('../util')
|
|
16
16
|
const SpanContext = require('./span_context')
|
|
17
17
|
|
|
18
|
+
const dateNow = Date.now
|
|
19
|
+
|
|
18
20
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
19
21
|
|
|
20
|
-
const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_STATE_TRACKING')
|
|
21
|
-
const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = getValueFromEnvSources('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS')
|
|
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'))
|
|
22
24
|
|
|
23
25
|
const unfinishedRegistry = createRegistry('unfinished')
|
|
24
26
|
const finishedRegistry = createRegistry('finished')
|
|
@@ -251,7 +253,7 @@ class DatadogSpan {
|
|
|
251
253
|
return
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING
|
|
256
|
+
if (DD_TRACE_EXPERIMENTAL_STATE_TRACKING && !this._spanContext._tags['service.name']) {
|
|
255
257
|
log.error('Finishing invalid span: %s', this)
|
|
256
258
|
}
|
|
257
259
|
|
|
@@ -63,6 +63,8 @@ const {
|
|
|
63
63
|
getPullRequestDiff,
|
|
64
64
|
getModifiedFilesFromDiff,
|
|
65
65
|
getPullRequestBaseBranch,
|
|
66
|
+
getSessionRequestErrorTags,
|
|
67
|
+
DD_CI_LIBRARY_CONFIGURATION_ERROR,
|
|
66
68
|
TEST_IS_TEST_FRAMEWORK_WORKER,
|
|
67
69
|
TEST_IS_NEW,
|
|
68
70
|
TEST_IS_RUM_ACTIVE,
|
|
@@ -120,6 +122,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
120
122
|
this.fileLineToProbeId = new Map()
|
|
121
123
|
this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
|
|
122
124
|
this._testSuiteSpansByTestSuite = new Map()
|
|
125
|
+
this._pendingRequestErrorTags = []
|
|
123
126
|
|
|
124
127
|
this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
|
|
125
128
|
const { onDone, isParallel, frameworkVersion } = ctx
|
|
@@ -131,10 +134,15 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
131
134
|
this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
|
|
132
135
|
if (err) {
|
|
133
136
|
log.error('Library configuration could not be fetched. %s', err.message)
|
|
137
|
+
this._addRequestErrorTag(DD_CI_LIBRARY_CONFIGURATION_ERROR, err)
|
|
134
138
|
} else {
|
|
135
139
|
this.libraryConfig = libraryConfig
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
const requestErrorTags = this.testSessionSpan
|
|
143
|
+
? getSessionRequestErrorTags(this.testSessionSpan)
|
|
144
|
+
: Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
|
|
145
|
+
|
|
138
146
|
const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, isParallel, frameworkVersion)
|
|
139
147
|
const metadataTags = {
|
|
140
148
|
test: {
|
|
@@ -142,7 +150,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
142
150
|
},
|
|
143
151
|
}
|
|
144
152
|
this.tracer._exporter.addMetadataTags(metadataTags)
|
|
145
|
-
onDone({ err, libraryConfig })
|
|
153
|
+
onDone({ err, libraryConfig, requestErrorTags })
|
|
146
154
|
})
|
|
147
155
|
})
|
|
148
156
|
|
|
@@ -200,6 +208,10 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
200
208
|
},
|
|
201
209
|
integrationName: this.constructor.id,
|
|
202
210
|
})
|
|
211
|
+
for (const { tag, value } of this._pendingRequestErrorTags) {
|
|
212
|
+
this.testSessionSpan.setTag(tag, value)
|
|
213
|
+
}
|
|
214
|
+
this._pendingRequestErrorTags = []
|
|
203
215
|
// TODO: add telemetry tag when we can add `is_agentless_log_submission_enabled` for agentless log submission
|
|
204
216
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
|
|
205
217
|
|
|
@@ -209,6 +221,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
209
221
|
[COMPONENT]: this.constructor.id,
|
|
210
222
|
...this.testEnvironmentMetadata,
|
|
211
223
|
...testModuleSpanMetadata,
|
|
224
|
+
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
212
225
|
},
|
|
213
226
|
integrationName: this.constructor.id,
|
|
214
227
|
})
|
|
@@ -230,7 +243,10 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
230
243
|
this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
|
|
231
244
|
const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
232
245
|
for (const testSuite of skippedSuites) {
|
|
233
|
-
const testSuiteMetadata =
|
|
246
|
+
const testSuiteMetadata = {
|
|
247
|
+
...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id),
|
|
248
|
+
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
249
|
+
}
|
|
234
250
|
if (this.itrCorrelationId) {
|
|
235
251
|
testSuiteMetadata[ITR_CORRELATION_ID] = this.itrCorrelationId
|
|
236
252
|
}
|
|
@@ -261,8 +277,10 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
261
277
|
this.tracer._exporter.getKnownTests(this.testConfiguration, (err, knownTests) => {
|
|
262
278
|
if (err) {
|
|
263
279
|
log.error('Known tests could not be fetched. %s', err.message)
|
|
264
|
-
this.libraryConfig
|
|
265
|
-
|
|
280
|
+
if (this.libraryConfig) {
|
|
281
|
+
this.libraryConfig.isEarlyFlakeDetectionEnabled = false
|
|
282
|
+
this.libraryConfig.isKnownTestsEnabled = false
|
|
283
|
+
}
|
|
266
284
|
}
|
|
267
285
|
onDone({ err, knownTests })
|
|
268
286
|
})
|
|
@@ -279,7 +297,9 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
279
297
|
this.tracer._exporter.getTestManagementTests(this.testConfiguration, (err, testManagementTests) => {
|
|
280
298
|
if (err) {
|
|
281
299
|
log.error('Test management tests could not be fetched. %s', err.message)
|
|
282
|
-
this.libraryConfig
|
|
300
|
+
if (this.libraryConfig) {
|
|
301
|
+
this.libraryConfig.isTestManagementEnabled = false
|
|
302
|
+
}
|
|
283
303
|
}
|
|
284
304
|
onDone({ err, testManagementTests })
|
|
285
305
|
})
|
|
@@ -346,8 +366,15 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
346
366
|
span.meta = {
|
|
347
367
|
...span.meta,
|
|
348
368
|
...testSuiteTags,
|
|
369
|
+
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
349
370
|
}
|
|
350
371
|
}
|
|
372
|
+
|
|
373
|
+
// Jest and Vitest worker test spans are serialized in the worker and may not include
|
|
374
|
+
// request error tags; add them from the session span in the main process.
|
|
375
|
+
if ((span.name === 'jest.test' || span.name === 'vitest.test') && this.testSessionSpan) {
|
|
376
|
+
Object.assign(span.meta, getSessionRequestErrorTags(this.testSessionSpan))
|
|
377
|
+
}
|
|
351
378
|
}
|
|
352
379
|
this.tracer._exporter.export(trace)
|
|
353
380
|
}
|
|
@@ -418,6 +445,30 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
418
445
|
}
|
|
419
446
|
}
|
|
420
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Adds a hidden _dd tag to the test session span when a test-optimization request fails.
|
|
450
|
+
* If the session span does not exist yet (e.g. library-configuration failed before session:start),
|
|
451
|
+
* the tag is queued and applied when the span is created.
|
|
452
|
+
* @param {string} tag - Tag name (e.g. DD_CI_LIBRARY_CONFIGURATION_ERROR)
|
|
453
|
+
* @param {Error} err - Request error
|
|
454
|
+
*/
|
|
455
|
+
_addRequestErrorTag (tag, err) {
|
|
456
|
+
const value = 'true'
|
|
457
|
+
if (this.testSessionSpan) {
|
|
458
|
+
this.testSessionSpan.setTag(tag, value)
|
|
459
|
+
} else {
|
|
460
|
+
this._pendingRequestErrorTags.push({ tag, value })
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Returns request error tags from the test session span for propagation to module, suite and test spans.
|
|
466
|
+
* @returns {Record<string, string>}
|
|
467
|
+
*/
|
|
468
|
+
getSessionRequestErrorTags () {
|
|
469
|
+
return getSessionRequestErrorTags(this.testSessionSpan)
|
|
470
|
+
}
|
|
471
|
+
|
|
421
472
|
configure (config, shouldGetEnvironmentData = true) {
|
|
422
473
|
super.configure(config)
|
|
423
474
|
|
|
@@ -529,6 +580,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
529
580
|
[TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
|
|
530
581
|
[TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
|
|
531
582
|
[TEST_MODULE]: this.constructor.id,
|
|
583
|
+
...getSessionRequestErrorTags(this.testSessionSpan),
|
|
532
584
|
}
|
|
533
585
|
if (testSuiteSpan.context()._parentId) {
|
|
534
586
|
suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { PEER_SERVICE_KEY, PEER_SERVICE_SOURCE_KEY } = require('../constants')
|
|
4
|
+
const propagationHash = require('../propagation-hash')
|
|
4
5
|
const StoragePlugin = require('./storage')
|
|
5
6
|
|
|
6
7
|
class DatabasePlugin extends StoragePlugin {
|
|
@@ -59,13 +60,25 @@ class DatabasePlugin extends StoragePlugin {
|
|
|
59
60
|
const dbmService = this.#getDbmServiceName(serviceName, peerData)
|
|
60
61
|
const servicePropagation = this.#createDBMPropagationCommentService(dbmService, span, peerData)
|
|
61
62
|
|
|
63
|
+
let dbmComment = servicePropagation
|
|
64
|
+
|
|
65
|
+
// Add propagation hash if both process tags and SQL base hash injection are enabled
|
|
66
|
+
if (propagationHash.isEnabled() && this.config['dbm.injectSqlBaseHash']) {
|
|
67
|
+
const hashBase64 = propagationHash.getHashBase64()
|
|
68
|
+
if (hashBase64) {
|
|
69
|
+
dbmComment += `,ddsh='${hashBase64}'`
|
|
70
|
+
// Add hash to span meta as a tag
|
|
71
|
+
span.setTag('_dd.dbm.propagation_hash', hashBase64)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
if (disableFullMode || mode === 'service') {
|
|
63
|
-
return
|
|
76
|
+
return dbmComment
|
|
64
77
|
} else if (mode === 'full') {
|
|
65
78
|
span.setTag('_dd.dbm_trace_injected', 'true')
|
|
66
79
|
span._processor.sample(span)
|
|
67
80
|
const traceparent = span._spanContext.toTraceparent()
|
|
68
|
-
return `${
|
|
81
|
+
return `${dbmComment},traceparent='${traceparent}'`
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
|
|
@@ -145,6 +145,10 @@ const DD_CAPABILITIES_TEST_MANAGEMENT_QUARANTINE = '_dd.library_capabilities.tes
|
|
|
145
145
|
const DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE = '_dd.library_capabilities.test_management.disable'
|
|
146
146
|
const DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX = '_dd.library_capabilities.test_management.attempt_to_fix'
|
|
147
147
|
const DD_CAPABILITIES_FAILED_TEST_REPLAY = '_dd.library_capabilities.failed_test_replay'
|
|
148
|
+
|
|
149
|
+
// Library configuration request error tag
|
|
150
|
+
const DD_CI_LIBRARY_CONFIGURATION_ERROR = '_dd.ci.library_configuration_error'
|
|
151
|
+
|
|
148
152
|
const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest'])
|
|
149
153
|
const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = new Set(['cucumber', 'mocha'])
|
|
150
154
|
const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
|
|
@@ -202,6 +206,22 @@ const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_f
|
|
|
202
206
|
const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
|
|
203
207
|
const BASE_LIKE_BRANCH_FILTER = /^(main|master|preprod|prod|dev|development|trunk|release\/.*|hotfix\/.*)$/
|
|
204
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Returns request error tags from a test session span for propagation to child events.
|
|
211
|
+
* @param {{ context: () => { _tags?: Record<string, string> } } | undefined} sessionSpan
|
|
212
|
+
* @returns {Record<string, string>}
|
|
213
|
+
*/
|
|
214
|
+
function getSessionRequestErrorTags (sessionSpan) {
|
|
215
|
+
const tags = sessionSpan?.context()._tags
|
|
216
|
+
if (!tags || typeof tags !== 'object') return {}
|
|
217
|
+
if (tags[DD_CI_LIBRARY_CONFIGURATION_ERROR] === 'true') {
|
|
218
|
+
return {
|
|
219
|
+
[DD_CI_LIBRARY_CONFIGURATION_ERROR]: 'true',
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {}
|
|
223
|
+
}
|
|
224
|
+
|
|
205
225
|
module.exports = {
|
|
206
226
|
TEST_CODE_OWNERS,
|
|
207
227
|
TEST_SESSION_NAME,
|
|
@@ -278,6 +298,7 @@ module.exports = {
|
|
|
278
298
|
removeInvalidMetadata,
|
|
279
299
|
parseAnnotations,
|
|
280
300
|
getIsFaultyEarlyFlakeDetection,
|
|
301
|
+
getEfdRetryCount,
|
|
281
302
|
TEST_BROWSER_DRIVER,
|
|
282
303
|
TEST_BROWSER_DRIVER_VERSION,
|
|
283
304
|
TEST_BROWSER_NAME,
|
|
@@ -308,6 +329,8 @@ module.exports = {
|
|
|
308
329
|
TEST_MANAGEMENT_ENABLED,
|
|
309
330
|
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
310
331
|
getLibraryCapabilitiesTags,
|
|
332
|
+
getSessionRequestErrorTags,
|
|
333
|
+
DD_CI_LIBRARY_CONFIGURATION_ERROR,
|
|
311
334
|
checkShaDiscrepancies,
|
|
312
335
|
getPullRequestDiff,
|
|
313
336
|
getPullRequestBaseBranch,
|
|
@@ -846,6 +869,31 @@ function parseAnnotations (annotations) {
|
|
|
846
869
|
}, {})
|
|
847
870
|
}
|
|
848
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Given a test's first-execution duration (ms) and the slow_test_retries map
|
|
874
|
+
* from the backend, return how many EFD retries to run.
|
|
875
|
+
*
|
|
876
|
+
* Returns 0 when the test is too slow to retry (≥ 5 min).
|
|
877
|
+
*
|
|
878
|
+
* @param {number} durationMs
|
|
879
|
+
* @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
|
|
880
|
+
* @returns {number}
|
|
881
|
+
*/
|
|
882
|
+
function getEfdRetryCount (durationMs, slowTestRetries) {
|
|
883
|
+
const thresholds = [
|
|
884
|
+
{ limitMs: 5 * 1000, key: '5s' },
|
|
885
|
+
{ limitMs: 10 * 1000, key: '10s' },
|
|
886
|
+
{ limitMs: 30 * 1000, key: '30s' },
|
|
887
|
+
{ limitMs: 5 * 60 * 1000, key: '5m' },
|
|
888
|
+
]
|
|
889
|
+
for (const { limitMs, key } of thresholds) {
|
|
890
|
+
if (durationMs < limitMs) {
|
|
891
|
+
return slowTestRetries[key] ?? 0
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return 0 // ≥ 5 min — abort
|
|
895
|
+
}
|
|
896
|
+
|
|
849
897
|
function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
|
|
850
898
|
let newSuites = 0
|
|
851
899
|
for (const suite of projectSuites) {
|
|
@@ -17,6 +17,7 @@ function exporterFromURL (url) {
|
|
|
17
17
|
if (url.protocol === 'file:') {
|
|
18
18
|
return new FileExporter({ pprofPrefix: fileURLToPath(url) })
|
|
19
19
|
}
|
|
20
|
+
// TODO: Why is DD_INJECTION_ENABLED a comma separated list?
|
|
20
21
|
const injectionEnabled = (getValueFromEnvSources('DD_INJECTION_ENABLED') ?? '').split(',')
|
|
21
22
|
const libraryInjected = injectionEnabled.length > 0
|
|
22
23
|
const profilingEnabled = (getValueFromEnvSources('DD_PROFILING_ENABLED') ?? '').toLowerCase()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { fnv64 } = require('../datastreams/fnv')
|
|
4
|
+
const log = require('../log')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PropagationHashManager is a singleton that manages the propagation hash computation.
|
|
8
|
+
* The propagation hash is an FNV-1a 64-bit hash combining:
|
|
9
|
+
* - Process tags (entrypoint info, package.json name, etc.)
|
|
10
|
+
* - Container tags hash (received from the Datadog agent)
|
|
11
|
+
*
|
|
12
|
+
* This hash is used to correlate traces with database operations (DBM) and
|
|
13
|
+
* data stream pathways (DSM) for enhanced observability.
|
|
14
|
+
*/
|
|
15
|
+
class PropagationHashManager {
|
|
16
|
+
_containerTagsHash = null
|
|
17
|
+
_cachedHash = null
|
|
18
|
+
_cachedHashString = null
|
|
19
|
+
_cachedHashBase64 = null
|
|
20
|
+
_config = null
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configure the propagation hash manager with tracer config
|
|
24
|
+
* @param {object} config - Tracer configuration
|
|
25
|
+
*/
|
|
26
|
+
configure (config) {
|
|
27
|
+
this._config = config
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if process tags propagation is enabled
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
isEnabled () {
|
|
35
|
+
return this._config?.propagateProcessTags?.enabled === true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Update the container tags hash received from the agent
|
|
40
|
+
* @param {string} hash - Container tags hash from agent response
|
|
41
|
+
*/
|
|
42
|
+
updateContainerTagsHash (hash) {
|
|
43
|
+
if (hash !== this._containerTagsHash) {
|
|
44
|
+
log.debug('Updating container tags hash: %s', hash)
|
|
45
|
+
this._containerTagsHash = hash
|
|
46
|
+
this._invalidateCache()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the propagation hash as a BigInt
|
|
52
|
+
* @returns {bigint | null} The propagation hash or null if disabled/unavailable
|
|
53
|
+
*/
|
|
54
|
+
getHash () {
|
|
55
|
+
if (!this.isEnabled()) {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
if (this._cachedHash) {
|
|
59
|
+
return this._cachedHash
|
|
60
|
+
}
|
|
61
|
+
this._computeHash()
|
|
62
|
+
return this._cachedHash
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the propagation hash as a hexadecimal string
|
|
67
|
+
* @returns {string|null} The propagation hash in hex format or null if disabled/unavailable
|
|
68
|
+
*/
|
|
69
|
+
getHashString () {
|
|
70
|
+
const hash = this.getHash()
|
|
71
|
+
if (!hash) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
if (!this._cachedHashString) {
|
|
75
|
+
this._cachedHashString = hash.toString(16)
|
|
76
|
+
}
|
|
77
|
+
return this._cachedHashString
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the propagation hash as a base64 string
|
|
82
|
+
* @returns {string|null} The propagation hash in base64 format or null if disabled/unavailable
|
|
83
|
+
*/
|
|
84
|
+
getHashBase64 () {
|
|
85
|
+
const hash = this.getHash()
|
|
86
|
+
if (!hash) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
if (!this._cachedHashBase64) {
|
|
90
|
+
// Convert BigInt to 8-byte buffer (64-bit hash)
|
|
91
|
+
const buffer = Buffer.allocUnsafe(8)
|
|
92
|
+
// Write as big-endian 64-bit unsigned integer
|
|
93
|
+
buffer.writeBigUInt64BE(hash, 0)
|
|
94
|
+
this._cachedHashBase64 = buffer.toString('base64')
|
|
95
|
+
}
|
|
96
|
+
return this._cachedHashBase64
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Compute the propagation hash using FNV-1a algorithm
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
_computeHash () {
|
|
104
|
+
try {
|
|
105
|
+
const processTags = require('../process-tags')
|
|
106
|
+
|
|
107
|
+
// Combine process tags and container tags hash
|
|
108
|
+
// Process tags are already serialized as a comma-separated string
|
|
109
|
+
const input = processTags.serialized + (this._containerTagsHash || '')
|
|
110
|
+
|
|
111
|
+
if (!input) {
|
|
112
|
+
// If both are empty, don't compute a hash
|
|
113
|
+
this._cachedHash = null
|
|
114
|
+
this._cachedHashString = null
|
|
115
|
+
this._cachedHashBase64 = null
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Compute FNV-1a 64-bit hash
|
|
120
|
+
this._cachedHash = fnv64(input)
|
|
121
|
+
this._cachedHashString = null // Will be computed on demand
|
|
122
|
+
this._cachedHashBase64 = null // Will be computed on demand
|
|
123
|
+
|
|
124
|
+
log.debug('Computed propagation hash from input (length=%s): "%s"', input.length, this._cachedHash.toString(16))
|
|
125
|
+
} catch (e) {
|
|
126
|
+
log.error('Error computing propagation hash', e)
|
|
127
|
+
this._cachedHash = null
|
|
128
|
+
this._cachedHashString = null
|
|
129
|
+
this._cachedHashBase64 = null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Invalidate the cached hash
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
_invalidateCache () {
|
|
138
|
+
this._cachedHash = null
|
|
139
|
+
this._cachedHashString = null
|
|
140
|
+
this._cachedHashBase64 = null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Export singleton instance
|
|
145
|
+
module.exports = new PropagationHashManager()
|
|
@@ -102,6 +102,10 @@ class Tracer extends NoopProxy {
|
|
|
102
102
|
try {
|
|
103
103
|
const config = getConfig(options) // TODO: support dynamic code config
|
|
104
104
|
|
|
105
|
+
// Configure propagation hash manager for process tags + container tags
|
|
106
|
+
const propagationHash = require('./propagation-hash')
|
|
107
|
+
propagationHash.configure(config)
|
|
108
|
+
|
|
105
109
|
if (config.crashtracking.enabled) {
|
|
106
110
|
require('./crashtracking').start(config)
|
|
107
111
|
}
|
|
@@ -238,7 +238,7 @@ function captureHeapSpace () {
|
|
|
238
238
|
const stats = v8.getHeapSpaceStatistics()
|
|
239
239
|
|
|
240
240
|
for (let i = 0, l = stats.length; i < l; i++) {
|
|
241
|
-
const tags = [`
|
|
241
|
+
const tags = [`heap_space:${stats[i].space_name}`]
|
|
242
242
|
|
|
243
243
|
client.gauge('runtime.node.heap.size.by.space', stats[i].space_size, tags)
|
|
244
244
|
client.gauge('runtime.node.heap.used_size.by.space', stats[i].space_used_size, tags)
|
|
@@ -74,7 +74,7 @@ function tracerInfo () {
|
|
|
74
74
|
runtime_metrics_enabled: !!config.runtimeMetrics,
|
|
75
75
|
profiling_enabled: config.profiling?.enabled === 'true' || config.profiling?.enabled === 'auto',
|
|
76
76
|
integrations_loaded: Object.keys(pluginManager._pluginsByName),
|
|
77
|
-
appsec_enabled:
|
|
77
|
+
appsec_enabled: config.appsec.enabled,
|
|
78
78
|
data_streams_enabled: !!config.dsmEnabled,
|
|
79
79
|
}
|
|
80
80
|
|