dd-trace 5.2.0 → 5.4.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 +1 -0
- package/README.md +1 -32
- package/ci/init.js +1 -4
- package/index.d.ts +21 -0
- package/package.json +7 -6
- package/packages/datadog-instrumentations/src/amqplib.js +1 -1
- package/packages/datadog-instrumentations/src/child_process.js +150 -0
- package/packages/datadog-instrumentations/src/cucumber.js +12 -12
- package/packages/datadog-instrumentations/src/express.js +20 -0
- package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +149 -11
- package/packages/datadog-instrumentations/src/mocha.js +142 -16
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/next.js +17 -3
- package/packages/datadog-instrumentations/src/playwright.js +41 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
- package/packages/datadog-plugin-child_process/src/index.js +91 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
- package/packages/datadog-plugin-cucumber/src/index.js +16 -11
- package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +43 -6
- package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
- package/packages/datadog-plugin-mocha/src/index.js +47 -17
- package/packages/datadog-plugin-playwright/src/index.js +19 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
- package/packages/datadog-plugin-rhea/src/producer.js +11 -0
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
- package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
- package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +12 -1
- package/packages/dd-trace/src/appsec/iast/index.js +4 -4
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +17 -2
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
- package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
- package/packages/dd-trace/src/config.js +22 -9
- package/packages/dd-trace/src/datastreams/processor.js +6 -0
- package/packages/dd-trace/src/datastreams/writer.js +2 -5
- package/packages/dd-trace/src/dogstatsd.js +3 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
- package/packages/dd-trace/src/exporters/common/request.js +21 -3
- package/packages/dd-trace/src/format.js +25 -1
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +9 -2
- package/packages/dd-trace/src/opentracing/span.js +38 -0
- package/packages/dd-trace/src/opentracing/span_context.js +12 -6
- package/packages/dd-trace/src/opentracing/tracer.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -0
- package/packages/dd-trace/src/plugins/util/test.js +53 -8
- package/packages/dd-trace/src/profiling/config.js +22 -22
- package/packages/dd-trace/src/proxy.js +31 -23
- package/packages/dd-trace/src/span_processor.js +5 -1
- package/packages/dd-trace/src/telemetry/index.js +6 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +0 -3
- package/packages/datadog-instrumentations/src/child-process.js +0 -29
- package/packages/dd-trace/src/plugins/util/exec.js +0 -34
|
@@ -13,6 +13,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
|
13
13
|
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
|
|
14
14
|
const { updateConfig } = require('./telemetry')
|
|
15
15
|
const { getIsGCPFunction, getIsAzureFunctionConsumptionPlan } = require('./serverless')
|
|
16
|
+
const { ORIGIN_KEY } = require('./constants')
|
|
16
17
|
|
|
17
18
|
const fromEntries = Object.fromEntries || (entries =>
|
|
18
19
|
entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
|
|
@@ -109,10 +110,6 @@ class Config {
|
|
|
109
110
|
log.use(this.logger)
|
|
110
111
|
log.toggle(this.debug, this.logLevel, this)
|
|
111
112
|
|
|
112
|
-
const DD_TRACING_ENABLED = coalesce(
|
|
113
|
-
process.env.DD_TRACING_ENABLED,
|
|
114
|
-
true
|
|
115
|
-
)
|
|
116
113
|
const DD_PROFILING_ENABLED = coalesce(
|
|
117
114
|
options.profiling, // TODO: remove when enabled by default
|
|
118
115
|
process.env.DD_EXPERIMENTAL_PROFILING_ENABLED,
|
|
@@ -172,6 +169,11 @@ class Config {
|
|
|
172
169
|
false
|
|
173
170
|
)
|
|
174
171
|
|
|
172
|
+
const DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED = coalesce(
|
|
173
|
+
process.env.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
|
|
174
|
+
true
|
|
175
|
+
)
|
|
176
|
+
|
|
175
177
|
const DD_TRACE_MEMCACHED_COMMAND_ENABLED = coalesce(
|
|
176
178
|
process.env.DD_TRACE_MEMCACHED_COMMAND_ENABLED,
|
|
177
179
|
false
|
|
@@ -416,10 +418,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
416
418
|
process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING,
|
|
417
419
|
'safe'
|
|
418
420
|
).toLowerCase()
|
|
419
|
-
const
|
|
421
|
+
const DD_API_SECURITY_ENABLED = coalesce(
|
|
420
422
|
appsec?.apiSecurity?.enabled,
|
|
421
|
-
isTrue(process.env.
|
|
422
|
-
|
|
423
|
+
process.env.DD_API_SECURITY_ENABLED && isTrue(process.env.DD_API_SECURITY_ENABLED),
|
|
424
|
+
process.env.DD_EXPERIMENTAL_API_SECURITY_ENABLED && isTrue(process.env.DD_EXPERIMENTAL_API_SECURITY_ENABLED),
|
|
425
|
+
true
|
|
423
426
|
)
|
|
424
427
|
const DD_API_SECURITY_REQUEST_SAMPLE_RATE = coalesce(
|
|
425
428
|
appsec?.apiSecurity?.requestSampling,
|
|
@@ -563,7 +566,6 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
563
566
|
|
|
564
567
|
const defaultFlushInterval = inAWSLambda ? 0 : 2000
|
|
565
568
|
|
|
566
|
-
this.tracing = !isFalse(DD_TRACING_ENABLED)
|
|
567
569
|
this.dbmPropagationMode = DD_DBM_PROPAGATION_MODE
|
|
568
570
|
this.dsmEnabled = isTrue(DD_DATA_STREAMS_ENABLED)
|
|
569
571
|
this.openAiLogsEnabled = DD_OPENAI_LOGS_ENABLED
|
|
@@ -636,7 +638,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
636
638
|
mode: DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING
|
|
637
639
|
},
|
|
638
640
|
apiSecurity: {
|
|
639
|
-
enabled:
|
|
641
|
+
enabled: DD_API_SECURITY_ENABLED,
|
|
640
642
|
// Coerce value between 0 and 1
|
|
641
643
|
requestSampling: Math.min(1, Math.max(0, DD_API_SECURITY_REQUEST_SAMPLE_RATE))
|
|
642
644
|
}
|
|
@@ -666,6 +668,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
666
668
|
|
|
667
669
|
this.gitMetadataEnabled = isTrue(DD_TRACE_GIT_METADATA_ENABLED)
|
|
668
670
|
this.isManualApiEnabled = this.isCiVisibility && isTrue(DD_CIVISIBILITY_MANUAL_API_ENABLED)
|
|
671
|
+
this.isEarlyFlakeDetectionEnabled = this.isCiVisibility && isTrue(DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED)
|
|
669
672
|
|
|
670
673
|
this.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
|
|
671
674
|
|
|
@@ -703,6 +706,12 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
703
706
|
'runtime-id': uuid()
|
|
704
707
|
})
|
|
705
708
|
|
|
709
|
+
if (this.isCiVisibility) {
|
|
710
|
+
tagger.add(this.tags, {
|
|
711
|
+
[ORIGIN_KEY]: 'ciapp-test'
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
|
|
706
715
|
if (this.gitMetadataEnabled) {
|
|
707
716
|
this.repositoryUrl = removeUserSensitiveInfo(
|
|
708
717
|
coalesce(
|
|
@@ -772,6 +781,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
772
781
|
this._setBoolean(defaults, 'logInjection', false)
|
|
773
782
|
this._setArray(defaults, 'headerTags', [])
|
|
774
783
|
this._setValue(defaults, 'tags', {})
|
|
784
|
+
this._setBoolean(defaults, 'tracing', true)
|
|
775
785
|
}
|
|
776
786
|
|
|
777
787
|
_applyEnvironment () {
|
|
@@ -785,6 +795,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
785
795
|
DD_TRACE_HEADER_TAGS,
|
|
786
796
|
DD_TRACE_SAMPLE_RATE,
|
|
787
797
|
DD_TRACE_TAGS,
|
|
798
|
+
DD_TRACING_ENABLED,
|
|
788
799
|
DD_VERSION
|
|
789
800
|
} = process.env
|
|
790
801
|
|
|
@@ -802,6 +813,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
802
813
|
this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
|
|
803
814
|
this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS)
|
|
804
815
|
this._setTags(env, 'tags', tags)
|
|
816
|
+
this._setBoolean(env, 'tracing', DD_TRACING_ENABLED)
|
|
805
817
|
}
|
|
806
818
|
|
|
807
819
|
_applyOptions (options) {
|
|
@@ -836,6 +848,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
836
848
|
this._setBoolean(opts, 'logInjection', options.log_injection_enabled)
|
|
837
849
|
this._setArray(opts, 'headerTags', headerTags)
|
|
838
850
|
this._setTags(opts, 'tags', tags)
|
|
851
|
+
this._setBoolean(opts, 'tracing', options.tracing_enabled)
|
|
839
852
|
}
|
|
840
853
|
|
|
841
854
|
_setBoolean (obj, name, value) {
|
|
@@ -153,6 +153,11 @@ function getMessageSize (message) {
|
|
|
153
153
|
return getSizeOrZero(key) + getSizeOrZero(value) + getHeadersSize(headers)
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function getAmqpMessageSize (message) {
|
|
157
|
+
const { headers, content } = message
|
|
158
|
+
return getSizeOrZero(content) + getHeadersSize(headers)
|
|
159
|
+
}
|
|
160
|
+
|
|
156
161
|
class TimeBuckets extends Map {
|
|
157
162
|
forTime (time) {
|
|
158
163
|
if (!this.has(time)) {
|
|
@@ -358,6 +363,7 @@ module.exports = {
|
|
|
358
363
|
getMessageSize,
|
|
359
364
|
getHeadersSize,
|
|
360
365
|
getSizeOrZero,
|
|
366
|
+
getAmqpMessageSize,
|
|
361
367
|
ENTRY_PARENT_HASH,
|
|
362
368
|
CONTEXT_PROPAGATION_KEY
|
|
363
369
|
}
|
|
@@ -15,13 +15,10 @@ function makeRequest (data, url, cb) {
|
|
|
15
15
|
'Datadog-Meta-Tracer-Version': pkg.version,
|
|
16
16
|
'Content-Type': 'application/msgpack',
|
|
17
17
|
'Content-Encoding': 'gzip'
|
|
18
|
-
}
|
|
18
|
+
},
|
|
19
|
+
url
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
options.protocol = url.protocol
|
|
22
|
-
options.hostname = url.hostname
|
|
23
|
-
options.port = url.port
|
|
24
|
-
|
|
25
22
|
log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
|
|
26
23
|
|
|
27
24
|
request(data, options, (err, res) => {
|
|
@@ -67,16 +67,14 @@ class DogStatsDClient {
|
|
|
67
67
|
request(buffer, this._httpOptions, (err) => {
|
|
68
68
|
if (err) {
|
|
69
69
|
log.error('HTTP error from agent: ' + err.stack)
|
|
70
|
-
if (err.status) {
|
|
70
|
+
if (err.status === 404) {
|
|
71
71
|
// Inside this if-block, we have connectivity to the agent, but
|
|
72
72
|
// we're not getting a 200 from the proxy endpoint. If it's a 404,
|
|
73
73
|
// then we know we'll never have the endpoint, so just clear out the
|
|
74
74
|
// options. Either way, we can give UDP a try.
|
|
75
|
-
|
|
76
|
-
this._httpOptions = null
|
|
77
|
-
}
|
|
78
|
-
this._sendUdp(queue)
|
|
75
|
+
this._httpOptions = null
|
|
79
76
|
}
|
|
77
|
+
this._sendUdp(queue)
|
|
80
78
|
}
|
|
81
79
|
})
|
|
82
80
|
}
|
|
@@ -16,6 +16,7 @@ const ALLOWED_CONTENT_TYPES = ['test_session_end', 'test_module_end', 'test_suit
|
|
|
16
16
|
const TEST_SUITE_KEYS_LENGTH = 12
|
|
17
17
|
const TEST_MODULE_KEYS_LENGTH = 11
|
|
18
18
|
const TEST_SESSION_KEYS_LENGTH = 10
|
|
19
|
+
const TEST_AND_SPAN_KEYS_LENGTH = 11
|
|
19
20
|
|
|
20
21
|
const INTAKE_SOFT_LIMIT = 2 * 1024 * 1024 // 2MB
|
|
21
22
|
|
|
@@ -145,9 +146,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
_encodeEventContent (bytes, content) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let totalKeysLength = keysLength
|
|
149
|
+
let totalKeysLength = TEST_AND_SPAN_KEYS_LENGTH
|
|
151
150
|
if (content.meta.test_session_id) {
|
|
152
151
|
totalKeysLength = totalKeysLength + 1
|
|
153
152
|
}
|
|
@@ -161,6 +160,9 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
161
160
|
if (itrCorrelationId) {
|
|
162
161
|
totalKeysLength = totalKeysLength + 1
|
|
163
162
|
}
|
|
163
|
+
if (content.type) {
|
|
164
|
+
totalKeysLength = totalKeysLength + 1
|
|
165
|
+
}
|
|
164
166
|
this._encodeMapPrefix(bytes, totalKeysLength)
|
|
165
167
|
if (content.type) {
|
|
166
168
|
this._encodeString(bytes, 'type')
|
|
@@ -7,6 +7,8 @@ const { Readable } = require('stream')
|
|
|
7
7
|
const http = require('http')
|
|
8
8
|
const https = require('https')
|
|
9
9
|
const { parse: urlParse } = require('url')
|
|
10
|
+
const zlib = require('zlib')
|
|
11
|
+
|
|
10
12
|
const docker = require('./docker')
|
|
11
13
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
12
14
|
const { storage } = require('../../../../datadog-core')
|
|
@@ -93,16 +95,31 @@ function request (data, options, callback) {
|
|
|
93
95
|
options.agent = isSecure ? httpsAgent : httpAgent
|
|
94
96
|
|
|
95
97
|
const onResponse = res => {
|
|
96
|
-
|
|
98
|
+
const chunks = []
|
|
97
99
|
|
|
98
100
|
res.setTimeout(timeout)
|
|
99
101
|
|
|
100
|
-
res.on('data', chunk => {
|
|
102
|
+
res.on('data', chunk => {
|
|
103
|
+
chunks.push(chunk)
|
|
104
|
+
})
|
|
101
105
|
res.on('end', () => {
|
|
102
106
|
activeRequests--
|
|
107
|
+
const buffer = Buffer.concat(chunks)
|
|
103
108
|
|
|
104
109
|
if (res.statusCode >= 200 && res.statusCode <= 299) {
|
|
105
|
-
|
|
110
|
+
const isGzip = res.headers['content-encoding'] === 'gzip'
|
|
111
|
+
if (isGzip) {
|
|
112
|
+
zlib.gunzip(buffer, (err, result) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
log.error(`Could not gunzip response: ${err.message}`)
|
|
115
|
+
callback(null, '', res.statusCode)
|
|
116
|
+
} else {
|
|
117
|
+
callback(null, result.toString(), res.statusCode)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
} else {
|
|
121
|
+
callback(null, buffer.toString(), res.statusCode)
|
|
122
|
+
}
|
|
106
123
|
} else {
|
|
107
124
|
let errorMessage = ''
|
|
108
125
|
try {
|
|
@@ -114,6 +131,7 @@ function request (data, options, callback) {
|
|
|
114
131
|
} catch (e) {
|
|
115
132
|
// ignore error
|
|
116
133
|
}
|
|
134
|
+
const responseData = buffer.toString()
|
|
117
135
|
if (responseData) {
|
|
118
136
|
errorMessage += ` Response from the endpoint: "${responseData}"`
|
|
119
137
|
}
|
|
@@ -33,6 +33,7 @@ const map = {
|
|
|
33
33
|
function format (span) {
|
|
34
34
|
const formatted = formatSpan(span)
|
|
35
35
|
|
|
36
|
+
extractSpanLinks(formatted, span)
|
|
36
37
|
extractRootTags(formatted, span)
|
|
37
38
|
extractChunkTags(formatted, span)
|
|
38
39
|
extractTags(formatted, span)
|
|
@@ -53,7 +54,8 @@ function formatSpan (span) {
|
|
|
53
54
|
meta: {},
|
|
54
55
|
metrics: {},
|
|
55
56
|
start: Math.round(span._startTime * 1e6),
|
|
56
|
-
duration: Math.round(span._duration * 1e6)
|
|
57
|
+
duration: Math.round(span._duration * 1e6),
|
|
58
|
+
links: []
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -64,6 +66,28 @@ function setSingleSpanIngestionTags (span, options) {
|
|
|
64
66
|
addTag({}, span.metrics, SPAN_SAMPLING_MAX_PER_SECOND, options.maxPerSecond)
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
function extractSpanLinks (trace, span) {
|
|
70
|
+
const links = []
|
|
71
|
+
if (span._links) {
|
|
72
|
+
for (const link of span._links) {
|
|
73
|
+
const { context, attributes } = link
|
|
74
|
+
const formattedLink = {}
|
|
75
|
+
|
|
76
|
+
formattedLink.trace_id = context.toTraceId(true)
|
|
77
|
+
formattedLink.span_id = context.toSpanId(true)
|
|
78
|
+
|
|
79
|
+
if (attributes && Object.keys(attributes).length > 0) {
|
|
80
|
+
formattedLink.attributes = attributes
|
|
81
|
+
}
|
|
82
|
+
if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
|
|
83
|
+
if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
|
|
84
|
+
|
|
85
|
+
links.push(formattedLink)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (links.length > 0) { trace.meta['_dd.span_links'] = JSON.stringify(links) }
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
function extractTags (trace, span) {
|
|
68
92
|
const context = span.context()
|
|
69
93
|
const origin = context._trace.origin
|
|
@@ -132,7 +132,8 @@ class Span {
|
|
|
132
132
|
tags: {
|
|
133
133
|
[SERVICE_NAME]: _tracer._service,
|
|
134
134
|
[RESOURCE_NAME]: spanName
|
|
135
|
-
}
|
|
135
|
+
},
|
|
136
|
+
links
|
|
136
137
|
}, _tracer._debug)
|
|
137
138
|
|
|
138
139
|
if (attributes) {
|
|
@@ -148,7 +149,6 @@ class Span {
|
|
|
148
149
|
// math for computing opentracing timestamps is apparently lossy...
|
|
149
150
|
this.startTime = hrStartTime
|
|
150
151
|
this.kind = kind
|
|
151
|
-
this.links = links
|
|
152
152
|
this._spanProcessor.onStart(this, context)
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -191,6 +191,13 @@ class Span {
|
|
|
191
191
|
return this
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
addLink (context, attributes) {
|
|
195
|
+
// extract dd context
|
|
196
|
+
const ddSpanContext = context._ddContext
|
|
197
|
+
this._ddSpan.addLink(ddSpanContext, attributes)
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
194
201
|
setStatus ({ code, message }) {
|
|
195
202
|
if (!this.ended && !this._hasStatus && code) {
|
|
196
203
|
this._hasStatus = true
|
|
@@ -26,6 +26,7 @@ const unfinishedRegistry = createRegistry('unfinished')
|
|
|
26
26
|
const finishedRegistry = createRegistry('finished')
|
|
27
27
|
|
|
28
28
|
const OTEL_ENABLED = !!process.env.DD_TRACE_OTEL_ENABLED
|
|
29
|
+
const ALLOWED = ['string', 'number', 'boolean']
|
|
29
30
|
|
|
30
31
|
const integrationCounters = {
|
|
31
32
|
span_created: {},
|
|
@@ -82,6 +83,9 @@ class DatadogSpan {
|
|
|
82
83
|
|
|
83
84
|
this._startTime = fields.startTime || this._getTime()
|
|
84
85
|
|
|
86
|
+
this._links = []
|
|
87
|
+
fields.links && fields.links.forEach(link => this.addLink(link.context, link.attributes))
|
|
88
|
+
|
|
85
89
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
86
90
|
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
87
91
|
runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
|
|
@@ -150,6 +154,13 @@ class DatadogSpan {
|
|
|
150
154
|
|
|
151
155
|
logEvent () {}
|
|
152
156
|
|
|
157
|
+
addLink (context, attributes) {
|
|
158
|
+
this._links.push({
|
|
159
|
+
context: context._ddContext ? context._ddContext : context,
|
|
160
|
+
attributes: this._sanitizeAttributes(attributes)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
finish (finishTime) {
|
|
154
165
|
if (this._duration !== undefined) {
|
|
155
166
|
return
|
|
@@ -185,6 +196,33 @@ class DatadogSpan {
|
|
|
185
196
|
this._processor.process(this)
|
|
186
197
|
}
|
|
187
198
|
|
|
199
|
+
_sanitizeAttributes (attributes = {}) {
|
|
200
|
+
const sanitizedAttributes = {}
|
|
201
|
+
|
|
202
|
+
const addArrayOrScalarAttributes = (key, maybeArray) => {
|
|
203
|
+
if (Array.isArray(maybeArray)) {
|
|
204
|
+
for (const subkey in maybeArray) {
|
|
205
|
+
addArrayOrScalarAttributes(`${key}.${subkey}`, maybeArray[subkey])
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
const maybeScalar = maybeArray
|
|
209
|
+
if (ALLOWED.includes(typeof maybeScalar)) {
|
|
210
|
+
// Wrap the value as a string if it's not already a string
|
|
211
|
+
sanitizedAttributes[key] = typeof maybeScalar === 'string' ? maybeScalar : String(maybeScalar)
|
|
212
|
+
} else {
|
|
213
|
+
log.warn(`Dropping span link attribute. It is not of an allowed type`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Object.entries(attributes).forEach(entry => {
|
|
219
|
+
const [key, value] = entry
|
|
220
|
+
addArrayOrScalarAttributes(key, value)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
return sanitizedAttributes
|
|
224
|
+
}
|
|
225
|
+
|
|
188
226
|
_createContext (parent, fields) {
|
|
189
227
|
let spanContext
|
|
190
228
|
let startTime
|
|
@@ -28,20 +28,26 @@ class DatadogSpanContext {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
toTraceId () {
|
|
31
|
+
toTraceId (get128bitId = false) {
|
|
32
|
+
if (get128bitId) {
|
|
33
|
+
return this._traceId.toBuffer().length <= 8 && this._trace.tags[TRACE_ID_128]
|
|
34
|
+
? this._trace.tags[TRACE_ID_128] + this._traceId.toString(16).padStart(16, '0')
|
|
35
|
+
: this._traceId.toString(16).padStart(32, '0')
|
|
36
|
+
}
|
|
32
37
|
return this._traceId.toString(10)
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
toSpanId () {
|
|
40
|
+
toSpanId (get128bitId = false) {
|
|
41
|
+
if (get128bitId) {
|
|
42
|
+
return this._spanId.toString(16).padStart(16, '0')
|
|
43
|
+
}
|
|
36
44
|
return this._spanId.toString(10)
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
toTraceparent () {
|
|
40
48
|
const flags = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
|
|
41
|
-
const traceId = this.
|
|
42
|
-
|
|
43
|
-
: this._traceId.toString(16).padStart(32, '0')
|
|
44
|
-
const spanId = this._spanId.toString(16).padStart(16, '0')
|
|
49
|
+
const traceId = this.toTraceId(true)
|
|
50
|
+
const spanId = this.toSpanId(true)
|
|
45
51
|
const version = (this._traceparent && this._traceparent.version) || '00'
|
|
46
52
|
return `${version}-${traceId}-${spanId}-${flags}`
|
|
47
53
|
}
|
|
@@ -61,7 +61,8 @@ class DatadogTracer {
|
|
|
61
61
|
startTime: options.startTime,
|
|
62
62
|
hostname: this._hostname,
|
|
63
63
|
traceId128BitGenerationEnabled: this._traceId128BitGenerationEnabled,
|
|
64
|
-
integrationName: options.integrationName
|
|
64
|
+
integrationName: options.integrationName,
|
|
65
|
+
links: options.links
|
|
65
66
|
}, this._debug)
|
|
66
67
|
|
|
67
68
|
span.addTags(this._config.tags)
|
|
@@ -27,7 +27,7 @@ const {
|
|
|
27
27
|
TELEMETRY_EVENT_CREATED,
|
|
28
28
|
TELEMETRY_ITR_SKIPPED
|
|
29
29
|
} = require('../ci-visibility/telemetry')
|
|
30
|
-
const { CI_PROVIDER_NAME, GIT_REPOSITORY_URL, GIT_COMMIT_SHA, GIT_BRANCH } = require('./util/tags')
|
|
30
|
+
const { CI_PROVIDER_NAME, GIT_REPOSITORY_URL, GIT_COMMIT_SHA, GIT_BRANCH, CI_WORKSPACE_PATH } = require('./util/tags')
|
|
31
31
|
const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
|
|
32
32
|
|
|
33
33
|
module.exports = class CiPlugin extends Plugin {
|
|
@@ -36,22 +36,22 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
36
36
|
|
|
37
37
|
this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
|
|
38
38
|
|
|
39
|
-
this.addSub(`ci:${this.constructor.id}:
|
|
40
|
-
if (!this.tracer._exporter || !this.tracer._exporter.
|
|
39
|
+
this.addSub(`ci:${this.constructor.id}:library-configuration`, ({ onDone }) => {
|
|
40
|
+
if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) {
|
|
41
41
|
return onDone({ err: new Error('CI Visibility was not initialized correctly') })
|
|
42
42
|
}
|
|
43
|
-
this.tracer._exporter.
|
|
43
|
+
this.tracer._exporter.getLibraryConfiguration(this.testConfiguration, (err, libraryConfig) => {
|
|
44
44
|
if (err) {
|
|
45
45
|
log.error(`Intelligent Test Runner configuration could not be fetched. ${err.message}`)
|
|
46
46
|
} else {
|
|
47
|
-
this.
|
|
47
|
+
this.libraryConfig = libraryConfig
|
|
48
48
|
}
|
|
49
|
-
onDone({ err,
|
|
49
|
+
onDone({ err, libraryConfig })
|
|
50
50
|
})
|
|
51
51
|
})
|
|
52
52
|
|
|
53
53
|
this.addSub(`ci:${this.constructor.id}:test-suite:skippable`, ({ onDone }) => {
|
|
54
|
-
if (!this.tracer._exporter
|
|
54
|
+
if (!this.tracer._exporter?.getSkippableSuites) {
|
|
55
55
|
return onDone({ err: new Error('CI Visibility was not initialized correctly') })
|
|
56
56
|
}
|
|
57
57
|
this.tracer._exporter.getSkippableSuites(this.testConfiguration, (err, skippableSuites, itrCorrelationId) => {
|
|
@@ -115,6 +115,18 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
115
115
|
})
|
|
116
116
|
this.telemetry.count(TELEMETRY_ITR_SKIPPED, { testLevel: 'suite' }, skippedSuites.length)
|
|
117
117
|
})
|
|
118
|
+
|
|
119
|
+
this.addSub(`ci:${this.constructor.id}:known-tests`, ({ onDone }) => {
|
|
120
|
+
if (!this.tracer._exporter?.getKnownTests) {
|
|
121
|
+
return onDone({ err: new Error('CI Visibility was not initialized correctly') })
|
|
122
|
+
}
|
|
123
|
+
this.tracer._exporter.getKnownTests(this.testConfiguration, (err, knownTests) => {
|
|
124
|
+
if (err) {
|
|
125
|
+
log.error(`Known tests could not be fetched. ${err.message}`)
|
|
126
|
+
}
|
|
127
|
+
onDone({ err, knownTests })
|
|
128
|
+
})
|
|
129
|
+
})
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
get telemetry () {
|
|
@@ -140,7 +152,6 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
140
152
|
configure (config) {
|
|
141
153
|
super.configure(config)
|
|
142
154
|
this.testEnvironmentMetadata = getTestEnvironmentMetadata(this.constructor.id, this.config)
|
|
143
|
-
this.codeOwnersEntries = getCodeOwnersFileEntries()
|
|
144
155
|
|
|
145
156
|
const {
|
|
146
157
|
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
@@ -151,9 +162,14 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
151
162
|
[RUNTIME_NAME]: runtimeName,
|
|
152
163
|
[RUNTIME_VERSION]: runtimeVersion,
|
|
153
164
|
[GIT_BRANCH]: branch,
|
|
154
|
-
[CI_PROVIDER_NAME]: ciProviderName
|
|
165
|
+
[CI_PROVIDER_NAME]: ciProviderName,
|
|
166
|
+
[CI_WORKSPACE_PATH]: repositoryRoot
|
|
155
167
|
} = this.testEnvironmentMetadata
|
|
156
168
|
|
|
169
|
+
this.repositoryRoot = repositoryRoot || process.cwd()
|
|
170
|
+
|
|
171
|
+
this.codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
|
|
172
|
+
|
|
157
173
|
this.isUnsupportedCIProvider = !ciProviderName
|
|
158
174
|
|
|
159
175
|
this.testConfiguration = {
|
|
@@ -23,6 +23,7 @@ module.exports = {
|
|
|
23
23
|
get 'aws-sdk' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
24
24
|
get 'bunyan' () { return require('../../../datadog-plugin-bunyan/src') },
|
|
25
25
|
get 'cassandra-driver' () { return require('../../../datadog-plugin-cassandra-driver/src') },
|
|
26
|
+
get 'child_process' () { return require('../../../datadog-plugin-child_process/src') },
|
|
26
27
|
get 'connect' () { return require('../../../datadog-plugin-connect/src') },
|
|
27
28
|
get 'couchbase' () { return require('../../../datadog-plugin-couchbase/src') },
|
|
28
29
|
get 'cypress' () { return require('../../../datadog-plugin-cypress/src') },
|
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
TELEMETRY_GIT_COMMAND_ERRORS
|
|
27
27
|
} = require('../../ci-visibility/telemetry')
|
|
28
28
|
const { filterSensitiveInfoFromRepository } = require('./url')
|
|
29
|
+
const { storage } = require('../../../../datadog-core')
|
|
29
30
|
|
|
30
31
|
const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
|
|
31
32
|
|
|
@@ -36,6 +37,9 @@ function sanitizedExec (
|
|
|
36
37
|
durationMetric,
|
|
37
38
|
errorMetric
|
|
38
39
|
) {
|
|
40
|
+
const store = storage.getStore()
|
|
41
|
+
storage.enterWith({ noop: true })
|
|
42
|
+
|
|
39
43
|
let startTime
|
|
40
44
|
if (operationMetric) {
|
|
41
45
|
incrementCountMetric(operationMetric.name, operationMetric.tags)
|
|
@@ -55,6 +59,8 @@ function sanitizedExec (
|
|
|
55
59
|
}
|
|
56
60
|
log.error(e)
|
|
57
61
|
return ''
|
|
62
|
+
} finally {
|
|
63
|
+
storage.enterWith(store)
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -48,6 +48,12 @@ const TEST_MODULE_ID = 'test_module_id'
|
|
|
48
48
|
const TEST_SUITE_ID = 'test_suite_id'
|
|
49
49
|
const TEST_TOOLCHAIN = 'test.toolchain'
|
|
50
50
|
const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
|
|
51
|
+
// Browser used in browser test. Namespaced by test.configuration because it affects the fingerprint
|
|
52
|
+
const TEST_CONFIGURATION_BROWSER_NAME = 'test.configuration.browser_name'
|
|
53
|
+
// Early flake detection
|
|
54
|
+
const TEST_IS_NEW = 'test.is_new'
|
|
55
|
+
const TEST_EARLY_FLAKE_IS_RETRY = 'test.early_flake.is_retry'
|
|
56
|
+
const TEST_EARLY_FLAKE_IS_ENABLED = 'test.early_flake.is_enabled'
|
|
51
57
|
|
|
52
58
|
const CI_APP_ORIGIN = 'ciapp-test'
|
|
53
59
|
|
|
@@ -68,6 +74,10 @@ const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
|
|
|
68
74
|
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
|
|
69
75
|
const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
|
|
70
76
|
|
|
77
|
+
// Early flake detection util strings
|
|
78
|
+
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
79
|
+
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
80
|
+
|
|
71
81
|
module.exports = {
|
|
72
82
|
TEST_CODE_OWNERS,
|
|
73
83
|
TEST_FRAMEWORK,
|
|
@@ -87,6 +97,10 @@ module.exports = {
|
|
|
87
97
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
88
98
|
TEST_SOURCE_START,
|
|
89
99
|
TEST_SKIPPED_BY_ITR,
|
|
100
|
+
TEST_CONFIGURATION_BROWSER_NAME,
|
|
101
|
+
TEST_IS_NEW,
|
|
102
|
+
TEST_EARLY_FLAKE_IS_RETRY,
|
|
103
|
+
TEST_EARLY_FLAKE_IS_ENABLED,
|
|
90
104
|
getTestEnvironmentMetadata,
|
|
91
105
|
getTestParametersString,
|
|
92
106
|
finishAllTraceSpans,
|
|
@@ -121,7 +135,11 @@ module.exports = {
|
|
|
121
135
|
getTestLineStart,
|
|
122
136
|
getCallSites,
|
|
123
137
|
removeInvalidMetadata,
|
|
124
|
-
parseAnnotations
|
|
138
|
+
parseAnnotations,
|
|
139
|
+
EFD_STRING,
|
|
140
|
+
EFD_TEST_NAME_REGEX,
|
|
141
|
+
removeEfdStringFromTestName,
|
|
142
|
+
addEfdStringToTestName
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -253,7 +271,6 @@ function getTestCommonTags (name, suite, version, testFramework) {
|
|
|
253
271
|
[SAMPLING_PRIORITY]: AUTO_KEEP,
|
|
254
272
|
[TEST_NAME]: name,
|
|
255
273
|
[TEST_SUITE]: suite,
|
|
256
|
-
[TEST_SOURCE_FILE]: suite,
|
|
257
274
|
[RESOURCE_NAME]: `${suite}.${name}`,
|
|
258
275
|
[TEST_FRAMEWORK_VERSION]: version,
|
|
259
276
|
[LIBRARY_VERSION]: ddTraceVersion
|
|
@@ -281,16 +298,36 @@ const POSSIBLE_CODEOWNERS_LOCATIONS = [
|
|
|
281
298
|
'.gitlab/CODEOWNERS'
|
|
282
299
|
]
|
|
283
300
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
POSSIBLE_CODEOWNERS_LOCATIONS.forEach(location => {
|
|
301
|
+
function readCodeOwners (rootDir) {
|
|
302
|
+
for (const location of POSSIBLE_CODEOWNERS_LOCATIONS) {
|
|
288
303
|
try {
|
|
289
|
-
|
|
304
|
+
return fs.readFileSync(path.join(rootDir, location)).toString()
|
|
290
305
|
} catch (e) {
|
|
291
306
|
// retry with next path
|
|
292
307
|
}
|
|
293
|
-
}
|
|
308
|
+
}
|
|
309
|
+
return ''
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function getCodeOwnersFileEntries (rootDir) {
|
|
313
|
+
let codeOwnersContent
|
|
314
|
+
let usedRootDir = rootDir
|
|
315
|
+
let isTriedCwd = false
|
|
316
|
+
|
|
317
|
+
const processCwd = process.cwd()
|
|
318
|
+
|
|
319
|
+
if (!usedRootDir || usedRootDir === processCwd) {
|
|
320
|
+
usedRootDir = processCwd
|
|
321
|
+
isTriedCwd = true
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
codeOwnersContent = readCodeOwners(usedRootDir)
|
|
325
|
+
|
|
326
|
+
// If we haven't found CODEOWNERS in the provided root dir, we try with process.cwd()
|
|
327
|
+
if (!codeOwnersContent && !isTriedCwd) {
|
|
328
|
+
codeOwnersContent = readCodeOwners(processCwd)
|
|
329
|
+
}
|
|
330
|
+
|
|
294
331
|
if (!codeOwnersContent) {
|
|
295
332
|
return null
|
|
296
333
|
}
|
|
@@ -523,3 +560,11 @@ function parseAnnotations (annotations) {
|
|
|
523
560
|
return tags
|
|
524
561
|
}, {})
|
|
525
562
|
}
|
|
563
|
+
|
|
564
|
+
function addEfdStringToTestName (testName, numAttempt) {
|
|
565
|
+
return `${EFD_STRING} (#${numAttempt}): ${testName}`
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function removeEfdStringFromTestName (testName) {
|
|
569
|
+
return testName.replace(EFD_TEST_NAME_REGEX, '')
|
|
570
|
+
}
|