dd-trace 3.46.0 → 3.48.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 -3
- package/README.md +1 -32
- package/ci/init.js +1 -4
- package/index.d.ts +21 -0
- package/package.json +6 -8
- package/packages/datadog-instrumentations/src/amqplib.js +2 -2
- 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 +3 -2
- package/packages/datadog-instrumentations/src/jest.js +147 -10
- package/packages/datadog-instrumentations/src/mocha.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/mquery.js +65 -0
- 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 +134 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +19 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -10
- 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 +25 -12
- package/packages/datadog-plugin-graphql/src/index.js +1 -6
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-grpc/src/util.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +47 -6
- package/packages/datadog-plugin-mocha/src/index.js +14 -5
- 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/nosql-injection-mongodb-analyzer.js +22 -17
- 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/iast-plugin.js +4 -1
- package/packages/dd-trace/src/appsec/index.js +17 -2
- 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 +36 -5
- package/packages/dd-trace/src/datastreams/writer.js +11 -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/propagation/text_map.js +1 -1
- 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 +24 -8
- 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 +36 -7
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- 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 +3 -0
- package/packages/dd-trace/src/tracer.js +1 -0
- package/packages/utils/src/kebabcase.js +16 -0
- package/packages/utils/src/pick.js +11 -0
- package/packages/utils/src/uniq.js +5 -0
- 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) {
|
|
@@ -125,6 +125,21 @@ function getSizeOrZero (obj) {
|
|
|
125
125
|
if (Buffer.isBuffer(obj)) {
|
|
126
126
|
return obj.length
|
|
127
127
|
}
|
|
128
|
+
if (Array.isArray(obj) && obj.length > 0) {
|
|
129
|
+
if (typeof obj[0] === 'number') return Buffer.from(obj).length
|
|
130
|
+
let payloadSize = 0
|
|
131
|
+
obj.forEach(item => {
|
|
132
|
+
payloadSize += getSizeOrZero(item)
|
|
133
|
+
})
|
|
134
|
+
return payloadSize
|
|
135
|
+
}
|
|
136
|
+
if (typeof obj === 'object') {
|
|
137
|
+
try {
|
|
138
|
+
return getHeadersSize(obj)
|
|
139
|
+
} catch {
|
|
140
|
+
// pass
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
return 0
|
|
129
144
|
}
|
|
130
145
|
|
|
@@ -138,6 +153,11 @@ function getMessageSize (message) {
|
|
|
138
153
|
return getSizeOrZero(key) + getSizeOrZero(value) + getHeadersSize(headers)
|
|
139
154
|
}
|
|
140
155
|
|
|
156
|
+
function getAmqpMessageSize (message) {
|
|
157
|
+
const { headers, content } = message
|
|
158
|
+
return getSizeOrZero(content) + getHeadersSize(headers)
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
class TimeBuckets extends Map {
|
|
142
162
|
forTime (time) {
|
|
143
163
|
if (!this.has(time)) {
|
|
@@ -157,7 +177,8 @@ class DataStreamsProcessor {
|
|
|
157
177
|
env,
|
|
158
178
|
tags,
|
|
159
179
|
version,
|
|
160
|
-
service
|
|
180
|
+
service,
|
|
181
|
+
flushInterval
|
|
161
182
|
} = {}) {
|
|
162
183
|
this.writer = new DataStreamsWriter({
|
|
163
184
|
hostname,
|
|
@@ -173,11 +194,13 @@ class DataStreamsProcessor {
|
|
|
173
194
|
this.service = service || 'unnamed-nodejs-service'
|
|
174
195
|
this.version = version || ''
|
|
175
196
|
this.sequence = 0
|
|
197
|
+
this.flushInterval = flushInterval
|
|
176
198
|
|
|
177
199
|
if (this.enabled) {
|
|
178
|
-
this.timer = setInterval(this.onInterval.bind(this),
|
|
200
|
+
this.timer = setInterval(this.onInterval.bind(this), flushInterval)
|
|
179
201
|
this.timer.unref()
|
|
180
202
|
}
|
|
203
|
+
process.once('beforeExit', () => this.onInterval())
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
onInterval () {
|
|
@@ -201,7 +224,8 @@ class DataStreamsProcessor {
|
|
|
201
224
|
*/
|
|
202
225
|
bucketFromTimestamp (timestamp) {
|
|
203
226
|
const bucketTime = Math.round(timestamp - (timestamp % this.bucketSizeNs))
|
|
204
|
-
|
|
227
|
+
const bucket = this.buckets.forTime(bucketTime)
|
|
228
|
+
return bucket
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
recordCheckpoint (checkpoint, span = null) {
|
|
@@ -259,8 +283,10 @@ class DataStreamsProcessor {
|
|
|
259
283
|
}
|
|
260
284
|
if (direction === 'direction:out') {
|
|
261
285
|
// Add the header for this now, as the callee doesn't have access to context when producing
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
// - 1 to account for extra byte for {
|
|
287
|
+
const ddInfoContinued = {}
|
|
288
|
+
ddInfoContinued[CONTEXT_PROPAGATION_KEY] = encodePathwayContext(dataStreamsContext).toJSON()
|
|
289
|
+
payloadSize += getSizeOrZero(JSON.stringify(ddInfoContinued)) - 1
|
|
264
290
|
}
|
|
265
291
|
const checkpoint = {
|
|
266
292
|
currentTimestamp: nowNs,
|
|
@@ -322,6 +348,10 @@ class DataStreamsProcessor {
|
|
|
322
348
|
Stats: serializedBuckets
|
|
323
349
|
}
|
|
324
350
|
}
|
|
351
|
+
|
|
352
|
+
setUrl (url) {
|
|
353
|
+
this.writer.setUrl(url)
|
|
354
|
+
}
|
|
325
355
|
}
|
|
326
356
|
|
|
327
357
|
module.exports = {
|
|
@@ -333,6 +363,7 @@ module.exports = {
|
|
|
333
363
|
getMessageSize,
|
|
334
364
|
getHeadersSize,
|
|
335
365
|
getSizeOrZero,
|
|
366
|
+
getAmqpMessageSize,
|
|
336
367
|
ENTRY_PARENT_HASH,
|
|
337
368
|
CONTEXT_PROPAGATION_KEY
|
|
338
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) => {
|
|
@@ -59,6 +56,15 @@ class DataStreamsWriter {
|
|
|
59
56
|
})
|
|
60
57
|
})
|
|
61
58
|
}
|
|
59
|
+
|
|
60
|
+
setUrl (url) {
|
|
61
|
+
try {
|
|
62
|
+
url = new URL(url)
|
|
63
|
+
this._url = url
|
|
64
|
+
} catch (e) {
|
|
65
|
+
log.warn(e.stack)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
module.exports = {
|
|
@@ -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,17 +36,17 @@ 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
|
|
|
@@ -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
|
|