dd-trace 4.36.0 → 4.38.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/README.md +8 -1
- package/ci/init.js +7 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +21 -6
- package/init.js +27 -3
- package/package.json +6 -6
- package/packages/datadog-esbuild/index.js +8 -2
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
- package/packages/datadog-instrumentations/src/cucumber.js +182 -105
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/lodash.js +31 -0
- package/packages/datadog-instrumentations/src/playwright.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
- package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
- package/packages/datadog-plugin-cucumber/src/index.js +83 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
- package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -33
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
- package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
- package/packages/dd-trace/src/config.js +25 -8
- package/packages/dd-trace/src/encode/0.4.js +50 -3
- package/packages/dd-trace/src/exporter.js +2 -1
- package/packages/dd-trace/src/plugins/database.js +20 -5
- package/packages/dd-trace/src/plugins/util/test.js +7 -0
- package/packages/dd-trace/src/profiling/profiler.js +23 -7
- package/packages/dd-trace/src/proxy.js +7 -1
- package/packages/dd-trace/src/serverless.js +3 -5
- package/packages/dd-trace/src/telemetry/index.js +9 -3
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +42 -1
- package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Writer = require('./writer')
|
|
4
|
+
const {
|
|
5
|
+
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
6
|
+
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
7
|
+
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
|
|
8
|
+
} = require('../../../plugins/util/test')
|
|
9
|
+
|
|
10
|
+
function getInterprocessTraceCode () {
|
|
11
|
+
if (process.env.JEST_WORKER_ID) {
|
|
12
|
+
return JEST_WORKER_TRACE_PAYLOAD_CODE
|
|
13
|
+
}
|
|
14
|
+
if (process.env.CUCUMBER_WORKER_ID) {
|
|
15
|
+
return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// TODO: make it available with cucumber
|
|
21
|
+
function getInterprocessCoverageCode () {
|
|
22
|
+
if (process.env.JEST_WORKER_ID) {
|
|
23
|
+
return JEST_WORKER_COVERAGE_PAYLOAD_CODE
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lightweight exporter whose writers only do simple JSON serialization
|
|
30
|
+
* of trace and coverage payloads, which they send to the test framework's main process.
|
|
31
|
+
* Currently used by Jest and Cucumber workers.
|
|
32
|
+
*/
|
|
33
|
+
class TestWorkerCiVisibilityExporter {
|
|
34
|
+
constructor () {
|
|
35
|
+
const interprocessTraceCode = getInterprocessTraceCode()
|
|
36
|
+
const interprocessCoverageCode = getInterprocessCoverageCode()
|
|
37
|
+
|
|
38
|
+
this._writer = new Writer(interprocessTraceCode)
|
|
39
|
+
this._coverageWriter = new Writer(interprocessCoverageCode)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export (payload) {
|
|
43
|
+
this._writer.append(payload)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exportCoverage (formattedCoverage) {
|
|
47
|
+
this._coverageWriter.append(formattedCoverage)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
flush () {
|
|
51
|
+
this._writer.flush()
|
|
52
|
+
this._coverageWriter.flush()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = TestWorkerCiVisibilityExporter
|
|
@@ -23,11 +23,18 @@ class Writer {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
_sendPayload (data) {
|
|
26
|
+
// ## Jest
|
|
26
27
|
// Only available when `child_process` is used for the jest worker.
|
|
27
28
|
// eslint-disable-next-line
|
|
28
29
|
// https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker
|
|
29
30
|
// If worker_threads is used, this will not work
|
|
30
31
|
// TODO: make it compatible with worker_threads
|
|
32
|
+
|
|
33
|
+
// ## Cucumber
|
|
34
|
+
// This reports to the test's main process the same way test data is reported by Cucumber
|
|
35
|
+
// See cucumber code:
|
|
36
|
+
// eslint-disable-next-line
|
|
37
|
+
// https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13
|
|
31
38
|
if (process.send) { // it only works if process.send is available
|
|
32
39
|
process.send([this._interprocessCode, data])
|
|
33
40
|
}
|
|
@@ -16,7 +16,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
|
16
16
|
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
|
|
17
17
|
const { updateConfig } = require('./telemetry')
|
|
18
18
|
const telemetryMetrics = require('./telemetry/metrics')
|
|
19
|
-
const { getIsGCPFunction,
|
|
19
|
+
const { getIsGCPFunction, getIsAzureFunction } = require('./serverless')
|
|
20
20
|
const { ORIGIN_KEY } = require('./constants')
|
|
21
21
|
|
|
22
22
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
@@ -339,7 +339,7 @@ class Config {
|
|
|
339
339
|
|
|
340
340
|
// Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent
|
|
341
341
|
this.memcachedCommandEnabled = isTrue(DD_TRACE_MEMCACHED_COMMAND_ENABLED)
|
|
342
|
-
this.
|
|
342
|
+
this.isAzureFunction = getIsAzureFunction()
|
|
343
343
|
this.spanLeakDebug = Number(DD_TRACE_SPAN_LEAK_DEBUG)
|
|
344
344
|
this.installSignature = {
|
|
345
345
|
id: DD_INSTRUMENTATION_INSTALL_ID,
|
|
@@ -417,8 +417,8 @@ class Config {
|
|
|
417
417
|
_isInServerlessEnvironment () {
|
|
418
418
|
const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
|
|
419
419
|
const isGCPFunction = getIsGCPFunction()
|
|
420
|
-
const
|
|
421
|
-
return inAWSLambda || isGCPFunction ||
|
|
420
|
+
const isAzureFunction = getIsAzureFunction()
|
|
421
|
+
return inAWSLambda || isGCPFunction || isAzureFunction
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
// for _merge to work, every config value must have a default value
|
|
@@ -446,6 +446,7 @@ class Config {
|
|
|
446
446
|
this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex)
|
|
447
447
|
this._setValue(defaults, 'appsec.rateLimit', 100)
|
|
448
448
|
this._setValue(defaults, 'appsec.rules', undefined)
|
|
449
|
+
this._setValue(defaults, 'appsec.sca.enabled', null)
|
|
449
450
|
this._setValue(defaults, 'appsec.wafTimeout', 5e3) // µs
|
|
450
451
|
this._setValue(defaults, 'clientIpEnabled', false)
|
|
451
452
|
this._setValue(defaults, 'clientIpHeader', null)
|
|
@@ -516,6 +517,7 @@ class Config {
|
|
|
516
517
|
this._setValue(defaults, 'tracing', true)
|
|
517
518
|
this._setValue(defaults, 'url', undefined)
|
|
518
519
|
this._setValue(defaults, 'version', pkg.version)
|
|
520
|
+
this._setValue(defaults, 'instrumentation_config_id', undefined)
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
_applyEnvironment () {
|
|
@@ -528,6 +530,7 @@ class Config {
|
|
|
528
530
|
DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
|
|
529
531
|
DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
|
|
530
532
|
DD_APPSEC_RULES,
|
|
533
|
+
DD_APPSEC_SCA_ENABLED,
|
|
531
534
|
DD_APPSEC_TRACE_RATE_LIMIT,
|
|
532
535
|
DD_APPSEC_WAF_TIMEOUT,
|
|
533
536
|
DD_DATA_STREAMS_ENABLED,
|
|
@@ -546,7 +549,9 @@ class Config {
|
|
|
546
549
|
DD_IAST_REDACTION_VALUE_PATTERN,
|
|
547
550
|
DD_IAST_REQUEST_SAMPLING,
|
|
548
551
|
DD_IAST_TELEMETRY_VERBOSITY,
|
|
552
|
+
DD_INJECTION_ENABLED,
|
|
549
553
|
DD_INSTRUMENTATION_TELEMETRY_ENABLED,
|
|
554
|
+
DD_INSTRUMENTATION_CONFIG_ID,
|
|
550
555
|
DD_LOGS_INJECTION,
|
|
551
556
|
DD_OPENAI_LOGS_ENABLED,
|
|
552
557
|
DD_OPENAI_SPAN_CHAR_LIMIT,
|
|
@@ -615,6 +620,8 @@ class Config {
|
|
|
615
620
|
this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
|
|
616
621
|
this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
|
|
617
622
|
this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
|
|
623
|
+
// DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
|
|
624
|
+
this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
|
|
618
625
|
this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
|
|
619
626
|
this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
|
|
620
627
|
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
|
|
@@ -695,10 +702,18 @@ class Config {
|
|
|
695
702
|
DD_INSTRUMENTATION_TELEMETRY_ENABLED, // to comply with instrumentation telemetry specs
|
|
696
703
|
!(this._isInServerlessEnvironment() || JEST_WORKER_ID)
|
|
697
704
|
))
|
|
705
|
+
this._setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID)
|
|
698
706
|
this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
|
|
699
707
|
this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
|
|
700
708
|
this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
|
|
701
|
-
|
|
709
|
+
const hasTelemetryLogsUsingFeatures =
|
|
710
|
+
isTrue(DD_IAST_ENABLED) ||
|
|
711
|
+
isTrue(DD_PROFILING_ENABLED) ||
|
|
712
|
+
(typeof DD_INJECTION_ENABLED === 'string' && DD_INJECTION_ENABLED.split(',').includes('profiling'))
|
|
713
|
+
? true
|
|
714
|
+
: undefined
|
|
715
|
+
this._setBoolean(env, 'telemetry.logCollection', coalesce(DD_TELEMETRY_LOG_COLLECTION_ENABLED,
|
|
716
|
+
hasTelemetryLogsUsingFeatures))
|
|
702
717
|
this._setBoolean(env, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED)
|
|
703
718
|
this._setBoolean(env, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
|
|
704
719
|
this._setBoolean(env, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
|
|
@@ -779,8 +794,10 @@ class Config {
|
|
|
779
794
|
this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
|
|
780
795
|
this._setBoolean(opts, 'startupLogs', options.startupLogs)
|
|
781
796
|
this._setTags(opts, 'tags', tags)
|
|
782
|
-
|
|
783
|
-
|
|
797
|
+
const hasTelemetryLogsUsingFeatures =
|
|
798
|
+
(options.iastOptions && (options.iastOptions === true || options.iastOptions?.enabled === true)) ||
|
|
799
|
+
(options.profiling && options.profiling === true)
|
|
800
|
+
this._setBoolean(opts, 'telemetry.logCollection', hasTelemetryLogsUsingFeatures)
|
|
784
801
|
this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
|
|
785
802
|
this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
|
|
786
803
|
this._setString(opts, 'version', options.version || tags.version)
|
|
@@ -860,7 +877,7 @@ class Config {
|
|
|
860
877
|
return coalesce(
|
|
861
878
|
this.options.stats,
|
|
862
879
|
process.env.DD_TRACE_STATS_COMPUTATION_ENABLED,
|
|
863
|
-
getIsGCPFunction() ||
|
|
880
|
+
getIsGCPFunction() || getIsAzureFunction()
|
|
864
881
|
)
|
|
865
882
|
}
|
|
866
883
|
|
|
@@ -83,13 +83,17 @@ class AgentEncoder {
|
|
|
83
83
|
span = formatSpan(span)
|
|
84
84
|
bytes.reserve(1)
|
|
85
85
|
|
|
86
|
-
if (span.type) {
|
|
86
|
+
if (span.type && span.meta_struct) {
|
|
87
|
+
bytes.buffer[bytes.length++] = 0x8d
|
|
88
|
+
} else if (span.type || span.meta_struct) {
|
|
87
89
|
bytes.buffer[bytes.length++] = 0x8c
|
|
90
|
+
} else {
|
|
91
|
+
bytes.buffer[bytes.length++] = 0x8b
|
|
92
|
+
}
|
|
88
93
|
|
|
94
|
+
if (span.type) {
|
|
89
95
|
this._encodeString(bytes, 'type')
|
|
90
96
|
this._encodeString(bytes, span.type)
|
|
91
|
-
} else {
|
|
92
|
-
bytes.buffer[bytes.length++] = 0x8b
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
this._encodeString(bytes, 'trace_id')
|
|
@@ -114,6 +118,10 @@ class AgentEncoder {
|
|
|
114
118
|
this._encodeMap(bytes, span.meta)
|
|
115
119
|
this._encodeString(bytes, 'metrics')
|
|
116
120
|
this._encodeMap(bytes, span.metrics)
|
|
121
|
+
if (span.meta_struct) {
|
|
122
|
+
this._encodeString(bytes, 'meta_struct')
|
|
123
|
+
this._encodeObject(bytes, span.meta_struct)
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
|
|
@@ -263,6 +271,45 @@ class AgentEncoder {
|
|
|
263
271
|
}
|
|
264
272
|
}
|
|
265
273
|
|
|
274
|
+
_encodeObject (bytes, value, circularReferencesDetector = new Set()) {
|
|
275
|
+
circularReferencesDetector.add(value)
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
return this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
|
|
278
|
+
} else if (value !== null && typeof value === 'object') {
|
|
279
|
+
return this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
|
|
280
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
281
|
+
this._encodeValue(bytes, value)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_encodeObjectAsMap (bytes, value, circularReferencesDetector) {
|
|
286
|
+
const keys = Object.keys(value)
|
|
287
|
+
const validKeys = keys.filter(key =>
|
|
288
|
+
typeof value[key] === 'string' ||
|
|
289
|
+
typeof value[key] === 'number' ||
|
|
290
|
+
(value[key] !== null && typeof value[key] === 'object' && !circularReferencesDetector.has(value[key])))
|
|
291
|
+
|
|
292
|
+
this._encodeMapPrefix(bytes, validKeys.length)
|
|
293
|
+
|
|
294
|
+
for (const key of validKeys) {
|
|
295
|
+
this._encodeString(bytes, key)
|
|
296
|
+
this._encodeObject(bytes, value[key], circularReferencesDetector)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_encodeObjectAsArray (bytes, value, circularReferencesDetector) {
|
|
301
|
+
const validValue = value.filter(item =>
|
|
302
|
+
typeof item === 'string' ||
|
|
303
|
+
typeof item === 'number' ||
|
|
304
|
+
(item !== null && typeof item === 'object' && !circularReferencesDetector.has(item)))
|
|
305
|
+
|
|
306
|
+
this._encodeArrayPrefix(bytes, validValue)
|
|
307
|
+
|
|
308
|
+
for (const item of validValue) {
|
|
309
|
+
this._encodeObject(bytes, item, circularReferencesDetector)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
266
313
|
_cacheString (value) {
|
|
267
314
|
if (!(value in this._stringMap)) {
|
|
268
315
|
this._stringCount++
|
|
@@ -18,7 +18,8 @@ module.exports = name => {
|
|
|
18
18
|
case exporters.AGENT_PROXY:
|
|
19
19
|
return require('./ci-visibility/exporters/agent-proxy')
|
|
20
20
|
case exporters.JEST_WORKER:
|
|
21
|
-
|
|
21
|
+
case exporters.CUCUMBER_WORKER:
|
|
22
|
+
return require('./ci-visibility/exporters/test-worker')
|
|
22
23
|
default:
|
|
23
24
|
return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent')
|
|
24
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const StoragePlugin = require('./storage')
|
|
4
|
-
const { PEER_SERVICE_KEY } = require('../constants')
|
|
4
|
+
const { PEER_SERVICE_KEY, PEER_SERVICE_SOURCE_KEY } = require('../constants')
|
|
5
5
|
|
|
6
6
|
class DatabasePlugin extends StoragePlugin {
|
|
7
7
|
static get operation () { return 'query' }
|
|
@@ -28,16 +28,31 @@ class DatabasePlugin extends StoragePlugin {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
createDBMPropagationCommentService (serviceName) {
|
|
31
|
+
createDBMPropagationCommentService (serviceName, span) {
|
|
32
32
|
this.encodingServiceTags('dddbs', 'encodedDddbs', serviceName)
|
|
33
33
|
this.encodingServiceTags('dde', 'encodedDde', this.tracer._env)
|
|
34
34
|
this.encodingServiceTags('ddps', 'encodedDdps', this.tracer._service)
|
|
35
35
|
this.encodingServiceTags('ddpv', 'encodedDdpv', this.tracer._version)
|
|
36
|
+
if (span.context()._tags['out.host']) {
|
|
37
|
+
this.encodingServiceTags('ddh', 'encodedDdh', span._spanContext._tags['out.host'])
|
|
38
|
+
}
|
|
39
|
+
if (span.context()._tags['db.name']) {
|
|
40
|
+
this.encodingServiceTags('dddb', 'encodedDddb', span._spanContext._tags['db.name'])
|
|
41
|
+
}
|
|
36
42
|
|
|
37
|
-
const { encodedDddbs, encodedDde, encodedDdps, encodedDdpv } = this.serviceTags
|
|
43
|
+
const { encodedDddb, encodedDddbs, encodedDde, encodedDdh, encodedDdps, encodedDdpv } = this.serviceTags
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
let dbmComment = `dddb='${encodedDddb}',dddbs='${encodedDddbs}',dde='${encodedDde}',ddh='${encodedDdh}',` +
|
|
40
46
|
`ddps='${encodedDdps}',ddpv='${encodedDdpv}'`
|
|
47
|
+
|
|
48
|
+
const peerData = this.getPeerService(span.context()._tags)
|
|
49
|
+
if (peerData !== undefined && peerData[PEER_SERVICE_SOURCE_KEY] === PEER_SERVICE_KEY) {
|
|
50
|
+
this.encodingServiceTags('ddprs', 'encodedDdprs', peerData[PEER_SERVICE_KEY])
|
|
51
|
+
|
|
52
|
+
const { encodedDdprs } = this.serviceTags
|
|
53
|
+
dbmComment += `,ddprs='${encodedDdprs}'`
|
|
54
|
+
}
|
|
55
|
+
return dbmComment
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
getDbmServiceName (span, tracerService) {
|
|
@@ -56,7 +71,7 @@ class DatabasePlugin extends StoragePlugin {
|
|
|
56
71
|
return query
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
const servicePropagation = this.createDBMPropagationCommentService(dbmService)
|
|
74
|
+
const servicePropagation = this.createDBMPropagationCommentService(dbmService, span)
|
|
60
75
|
|
|
61
76
|
if (isPreparedStatement || mode === 'service') {
|
|
62
77
|
return `/*${servicePropagation}*/ ${query}`
|
|
@@ -61,6 +61,8 @@ const CI_APP_ORIGIN = 'ciapp-test'
|
|
|
61
61
|
const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
62
62
|
const JEST_DISPLAY_NAME = 'test.jest.display_name'
|
|
63
63
|
|
|
64
|
+
const CUCUMBER_IS_PARALLEL = 'test.cucumber.is_parallel'
|
|
65
|
+
|
|
64
66
|
const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
|
|
65
67
|
const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
|
|
66
68
|
const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
|
|
@@ -82,6 +84,9 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
|
|
|
82
84
|
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
|
|
83
85
|
const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
|
|
84
86
|
|
|
87
|
+
// cucumber worker variables
|
|
88
|
+
const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
|
|
89
|
+
|
|
85
90
|
// Early flake detection util strings
|
|
86
91
|
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
87
92
|
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
@@ -92,6 +97,7 @@ module.exports = {
|
|
|
92
97
|
TEST_FRAMEWORK_VERSION,
|
|
93
98
|
JEST_TEST_RUNNER,
|
|
94
99
|
JEST_DISPLAY_NAME,
|
|
100
|
+
CUCUMBER_IS_PARALLEL,
|
|
95
101
|
TEST_TYPE,
|
|
96
102
|
TEST_NAME,
|
|
97
103
|
TEST_SUITE,
|
|
@@ -104,6 +110,7 @@ module.exports = {
|
|
|
104
110
|
LIBRARY_VERSION,
|
|
105
111
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
106
112
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
113
|
+
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
107
114
|
TEST_SOURCE_START,
|
|
108
115
|
TEST_SKIPPED_BY_ITR,
|
|
109
116
|
TEST_CONFIGURATION_BROWSER_NAME,
|
|
@@ -5,6 +5,7 @@ const { Config } = require('./config')
|
|
|
5
5
|
const { snapshotKinds } = require('./constants')
|
|
6
6
|
const { threadNamePrefix } = require('./profilers/shared')
|
|
7
7
|
const dc = require('dc-polyfill')
|
|
8
|
+
const telemetryLog = dc.channel('datadog:telemetry:log')
|
|
8
9
|
|
|
9
10
|
const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
|
|
10
11
|
|
|
@@ -15,6 +16,19 @@ function maybeSourceMap (sourceMap, SourceMapper, debug) {
|
|
|
15
16
|
], debug)
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function logError (logger, err) {
|
|
20
|
+
if (logger) {
|
|
21
|
+
logger.error(err)
|
|
22
|
+
}
|
|
23
|
+
if (telemetryLog.hasSubscribers) {
|
|
24
|
+
telemetryLog.publish({
|
|
25
|
+
message: err.message,
|
|
26
|
+
level: 'ERROR',
|
|
27
|
+
stack_trace: err.stack
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
class Profiler extends EventEmitter {
|
|
19
33
|
constructor () {
|
|
20
34
|
super()
|
|
@@ -28,13 +42,15 @@ class Profiler extends EventEmitter {
|
|
|
28
42
|
|
|
29
43
|
start (options) {
|
|
30
44
|
return this._start(options).catch((err) => {
|
|
31
|
-
|
|
32
|
-
options.logger.error(err)
|
|
33
|
-
}
|
|
45
|
+
logError(options.logger, err)
|
|
34
46
|
return false
|
|
35
47
|
})
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
_logError (err) {
|
|
51
|
+
logError(this._logger, err)
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
async _start (options) {
|
|
39
55
|
if (this._enabled) return true
|
|
40
56
|
|
|
@@ -61,7 +77,7 @@ class Profiler extends EventEmitter {
|
|
|
61
77
|
})
|
|
62
78
|
}
|
|
63
79
|
} catch (err) {
|
|
64
|
-
this.
|
|
80
|
+
this._logError(err)
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
try {
|
|
@@ -78,7 +94,7 @@ class Profiler extends EventEmitter {
|
|
|
78
94
|
this._capture(this._timeoutInterval, start)
|
|
79
95
|
return true
|
|
80
96
|
} catch (e) {
|
|
81
|
-
this.
|
|
97
|
+
this._logError(e)
|
|
82
98
|
this._stop()
|
|
83
99
|
return false
|
|
84
100
|
}
|
|
@@ -167,7 +183,7 @@ class Profiler extends EventEmitter {
|
|
|
167
183
|
profileSubmittedChannel.publish()
|
|
168
184
|
this._logger.debug('Submitted profiles')
|
|
169
185
|
} catch (err) {
|
|
170
|
-
this.
|
|
186
|
+
this._logError(err)
|
|
171
187
|
this._stop()
|
|
172
188
|
}
|
|
173
189
|
}
|
|
@@ -182,7 +198,7 @@ class Profiler extends EventEmitter {
|
|
|
182
198
|
tags.snapshot = snapshotKind
|
|
183
199
|
for (const exporter of this._config.exporters) {
|
|
184
200
|
const task = exporter.export({ profiles, start, end, tags })
|
|
185
|
-
.catch(err => this.
|
|
201
|
+
.catch(err => this._logError(err))
|
|
186
202
|
|
|
187
203
|
tasks.push(task)
|
|
188
204
|
}
|
|
@@ -14,6 +14,7 @@ const dogstatsd = require('./dogstatsd')
|
|
|
14
14
|
const NoopDogStatsDClient = require('./noop/dogstatsd')
|
|
15
15
|
const spanleak = require('./spanleak')
|
|
16
16
|
const { SSITelemetry } = require('./profiling/ssi-telemetry')
|
|
17
|
+
const telemetryLog = require('dc-polyfill').channel('datadog:telemetry:log')
|
|
17
18
|
|
|
18
19
|
class LazyModule {
|
|
19
20
|
constructor (provider) {
|
|
@@ -91,7 +92,7 @@ class Tracer extends NoopProxy {
|
|
|
91
92
|
})
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
if (config.isGCPFunction || config.
|
|
95
|
+
if (config.isGCPFunction || config.isAzureFunction) {
|
|
95
96
|
require('./serverless').maybeStartServerlessMiniAgent(config)
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -104,6 +105,11 @@ class Tracer extends NoopProxy {
|
|
|
104
105
|
this._profilerStarted = profiler.start(config)
|
|
105
106
|
} catch (e) {
|
|
106
107
|
log.error(e)
|
|
108
|
+
telemetryLog.publish({
|
|
109
|
+
message: e.message,
|
|
110
|
+
level: 'ERROR',
|
|
111
|
+
stack_trace: e.stack
|
|
112
|
+
})
|
|
107
113
|
}
|
|
108
114
|
} else if (ssiTelemetry.enabled()) {
|
|
109
115
|
require('./profiling/ssi-telemetry-mock-profiler').start(config)
|
|
@@ -53,18 +53,16 @@ function getIsGCPFunction () {
|
|
|
53
53
|
return isDeprecatedGCPFunction || isNewerGCPFunction
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function
|
|
56
|
+
function getIsAzureFunction () {
|
|
57
57
|
const isAzureFunction =
|
|
58
58
|
process.env.FUNCTIONS_EXTENSION_VERSION !== undefined && process.env.FUNCTIONS_WORKER_RUNTIME !== undefined
|
|
59
|
-
const azureWebsiteSKU = process.env.WEBSITE_SKU
|
|
60
|
-
const isConsumptionPlan = azureWebsiteSKU === undefined || azureWebsiteSKU === 'Dynamic'
|
|
61
59
|
|
|
62
|
-
return isAzureFunction
|
|
60
|
+
return isAzureFunction
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
module.exports = {
|
|
66
64
|
maybeStartServerlessMiniAgent,
|
|
67
65
|
getIsGCPFunction,
|
|
68
|
-
|
|
66
|
+
getIsAzureFunction,
|
|
69
67
|
getRustBinaryPath
|
|
70
68
|
}
|
|
@@ -6,7 +6,8 @@ const dependencies = require('./dependencies')
|
|
|
6
6
|
const { sendData } = require('./send-data')
|
|
7
7
|
const { errors } = require('../startup-log')
|
|
8
8
|
const { manager: metricsManager } = require('./metrics')
|
|
9
|
-
const
|
|
9
|
+
const telemetryLogger = require('./logs')
|
|
10
|
+
const logger = require('../log')
|
|
10
11
|
|
|
11
12
|
const telemetryStartChannel = dc.channel('datadog:telemetry:start')
|
|
12
13
|
const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
|
|
@@ -211,7 +212,7 @@ function createPayload (currReqType, currPayload = {}) {
|
|
|
211
212
|
function heartbeat (config, application, host) {
|
|
212
213
|
heartbeatTimeout = setTimeout(() => {
|
|
213
214
|
metricsManager.send(config, application, host)
|
|
214
|
-
|
|
215
|
+
telemetryLogger.send(config, application, host)
|
|
215
216
|
|
|
216
217
|
const { reqType, payload } = createPayload('app-heartbeat')
|
|
217
218
|
sendData(config, application, host, reqType, payload, updateRetryData)
|
|
@@ -235,6 +236,10 @@ function extendedHeartbeat (config) {
|
|
|
235
236
|
|
|
236
237
|
function start (aConfig, thePluginManager) {
|
|
237
238
|
if (!aConfig.telemetry.enabled) {
|
|
239
|
+
if (aConfig.sca?.enabled) {
|
|
240
|
+
logger.warn('DD_APPSEC_SCA_ENABLED requires enabling telemetry to work.')
|
|
241
|
+
}
|
|
242
|
+
|
|
238
243
|
return
|
|
239
244
|
}
|
|
240
245
|
config = aConfig
|
|
@@ -245,7 +250,7 @@ function start (aConfig, thePluginManager) {
|
|
|
245
250
|
integrations = getIntegrations()
|
|
246
251
|
|
|
247
252
|
dependencies.start(config, application, host, getRetryData, updateRetryData)
|
|
248
|
-
|
|
253
|
+
telemetryLogger.start(config)
|
|
249
254
|
|
|
250
255
|
sendData(config, application, host, 'app-started', appStarted(config))
|
|
251
256
|
|
|
@@ -318,6 +323,7 @@ function updateConfig (changes, config) {
|
|
|
318
323
|
|
|
319
324
|
for (const change of changes) {
|
|
320
325
|
const name = nameMapping[change.name] || change.name
|
|
326
|
+
|
|
321
327
|
names.push(name)
|
|
322
328
|
const { origin, value } = change
|
|
323
329
|
const entry = { name, value, origin }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../../log')
|
|
4
|
+
const { calculateDDBasePath } = require('../../util')
|
|
4
5
|
|
|
5
6
|
const logs = new Map()
|
|
6
7
|
|
|
@@ -29,6 +30,37 @@ function isValid (logEntry) {
|
|
|
29
30
|
return logEntry?.level && logEntry.message
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
const ddBasePath = calculateDDBasePath(__dirname)
|
|
34
|
+
const EOL = '\n'
|
|
35
|
+
const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
|
|
36
|
+
|
|
37
|
+
function sanitize (logEntry) {
|
|
38
|
+
const stack = logEntry.stack_trace
|
|
39
|
+
if (!stack) return logEntry
|
|
40
|
+
|
|
41
|
+
let stackLines = stack.split(EOL)
|
|
42
|
+
|
|
43
|
+
const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
|
|
44
|
+
|
|
45
|
+
const isDDCode = firstIndex > -1 && stackLines[firstIndex].includes(ddBasePath)
|
|
46
|
+
stackLines = stackLines
|
|
47
|
+
.filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
|
|
48
|
+
.map(line => line.replace(ddBasePath, ''))
|
|
49
|
+
|
|
50
|
+
logEntry.stack_trace = stackLines.join(EOL)
|
|
51
|
+
if (logEntry.stack_trace === '') {
|
|
52
|
+
// If entire stack was removed, we'd just have a message saying "omitted"
|
|
53
|
+
// in which case we'd rather not log it at all.
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isDDCode) {
|
|
58
|
+
logEntry.message = 'omitted'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return logEntry
|
|
62
|
+
}
|
|
63
|
+
|
|
32
64
|
const logCollector = {
|
|
33
65
|
add (logEntry) {
|
|
34
66
|
try {
|
|
@@ -37,9 +69,13 @@ const logCollector = {
|
|
|
37
69
|
// NOTE: should errors have higher priority? and discard log entries with lower priority?
|
|
38
70
|
if (logs.size >= maxEntries) {
|
|
39
71
|
overflowedCount++
|
|
40
|
-
return
|
|
72
|
+
return false
|
|
41
73
|
}
|
|
42
74
|
|
|
75
|
+
logEntry = sanitize(logEntry)
|
|
76
|
+
if (!logEntry) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
43
79
|
const hash = createHash(logEntry)
|
|
44
80
|
if (!logs.has(hash)) {
|
|
45
81
|
logs.set(hash, logEntry)
|
|
@@ -51,6 +87,11 @@ const logCollector = {
|
|
|
51
87
|
return false
|
|
52
88
|
},
|
|
53
89
|
|
|
90
|
+
// Used for testing
|
|
91
|
+
hasEntry (logEntry) {
|
|
92
|
+
return logs.has(createHash(logEntry))
|
|
93
|
+
},
|
|
94
|
+
|
|
54
95
|
drain () {
|
|
55
96
|
if (logs.size === 0) return
|
|
56
97
|
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Writer = require('./writer')
|
|
4
|
-
const {
|
|
5
|
-
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
6
|
-
JEST_WORKER_TRACE_PAYLOAD_CODE
|
|
7
|
-
} = require('../../../plugins/util/test')
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Lightweight exporter whose writers only do simple JSON serialization
|
|
11
|
-
* of trace and coverage payloads, which they send to the jest main process.
|
|
12
|
-
*/
|
|
13
|
-
class JestWorkerCiVisibilityExporter {
|
|
14
|
-
constructor () {
|
|
15
|
-
this._writer = new Writer(JEST_WORKER_TRACE_PAYLOAD_CODE)
|
|
16
|
-
this._coverageWriter = new Writer(JEST_WORKER_COVERAGE_PAYLOAD_CODE)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export (payload) {
|
|
20
|
-
this._writer.append(payload)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
exportCoverage (formattedCoverage) {
|
|
24
|
-
this._coverageWriter.append(formattedCoverage)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
flush () {
|
|
28
|
-
this._writer.flush()
|
|
29
|
-
this._coverageWriter.flush()
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
module.exports = JestWorkerCiVisibilityExporter
|