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
|
@@ -73,14 +73,41 @@ function getRootDir (playwrightRunner) {
|
|
|
73
73
|
if (playwrightRunner._configDir) {
|
|
74
74
|
return playwrightRunner._configDir
|
|
75
75
|
}
|
|
76
|
-
if (playwrightRunner._config
|
|
77
|
-
return playwrightRunner._config.config.
|
|
76
|
+
if (playwrightRunner._config) {
|
|
77
|
+
return playwrightRunner._config.config?.rootDir || process.cwd()
|
|
78
78
|
}
|
|
79
79
|
return process.cwd()
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
83
|
-
const
|
|
82
|
+
function getProjectsFromRunner (runner) {
|
|
83
|
+
const config = getPlaywrightConfig(runner)
|
|
84
|
+
return config.projects?.map(({ project }) => project)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getProjectsFromDispatcher (dispatcher) {
|
|
88
|
+
const newConfig = dispatcher._config?.config?.projects
|
|
89
|
+
if (newConfig) {
|
|
90
|
+
return newConfig
|
|
91
|
+
}
|
|
92
|
+
// old
|
|
93
|
+
return dispatcher._loader?.fullConfig()?.projects
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getBrowserNameFromProjects (projects, projectId) {
|
|
97
|
+
if (!projects) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
return projects.find(project =>
|
|
101
|
+
project.__projectId === projectId || project._id === projectId
|
|
102
|
+
)?.name
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function testBeginHandler (test, browserName) {
|
|
106
|
+
const {
|
|
107
|
+
_requireFile: testSuiteAbsolutePath,
|
|
108
|
+
title: testName, _type,
|
|
109
|
+
location: { line: testSourceLine }
|
|
110
|
+
} = test
|
|
84
111
|
|
|
85
112
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
86
113
|
return
|
|
@@ -100,7 +127,7 @@ function testBeginHandler (test) {
|
|
|
100
127
|
const testAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
101
128
|
testToAr.set(test, testAsyncResource)
|
|
102
129
|
testAsyncResource.runInAsyncScope(() => {
|
|
103
|
-
testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine })
|
|
130
|
+
testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine, browserName })
|
|
104
131
|
})
|
|
105
132
|
}
|
|
106
133
|
|
|
@@ -166,11 +193,12 @@ function dispatcherHook (dispatcherExport) {
|
|
|
166
193
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
167
194
|
const dispatcher = this
|
|
168
195
|
const worker = createWorker.apply(this, arguments)
|
|
169
|
-
|
|
170
196
|
worker.process.on('message', ({ method, params }) => {
|
|
171
197
|
if (method === 'testBegin') {
|
|
172
198
|
const { test } = dispatcher._testById.get(params.testId)
|
|
173
|
-
|
|
199
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
200
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
201
|
+
testBeginHandler(test, browser)
|
|
174
202
|
} else if (method === 'testEnd') {
|
|
175
203
|
const { test } = dispatcher._testById.get(params.testId)
|
|
176
204
|
|
|
@@ -203,7 +231,9 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
203
231
|
|
|
204
232
|
worker.on('testBegin', ({ testId }) => {
|
|
205
233
|
const test = getTestByTestId(dispatcher, testId)
|
|
206
|
-
|
|
234
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
235
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
236
|
+
testBeginHandler(test, browser)
|
|
207
237
|
})
|
|
208
238
|
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
209
239
|
const test = getTestByTestId(dispatcher, testId)
|
|
@@ -226,6 +256,7 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
226
256
|
testSessionAsyncResource.runInAsyncScope(() => {
|
|
227
257
|
testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
|
|
228
258
|
})
|
|
259
|
+
const projects = getProjectsFromRunner(this)
|
|
229
260
|
|
|
230
261
|
const runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
231
262
|
|
|
@@ -234,7 +265,8 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
234
265
|
// there were tests that did not go through `testBegin` or `testEnd`,
|
|
235
266
|
// because they were skipped
|
|
236
267
|
tests.forEach(test => {
|
|
237
|
-
|
|
268
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
269
|
+
testBeginHandler(test, browser)
|
|
238
270
|
testEndHandler(test, [], 'skip')
|
|
239
271
|
})
|
|
240
272
|
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
4
4
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
5
|
+
const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
|
|
5
6
|
const { getResourceName } = require('./util')
|
|
6
7
|
|
|
7
8
|
class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
@@ -13,7 +14,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
|
13
14
|
|
|
14
15
|
const childOf = extract(this.tracer, message)
|
|
15
16
|
|
|
16
|
-
this.startSpan({
|
|
17
|
+
const span = this.startSpan({
|
|
17
18
|
childOf,
|
|
18
19
|
resource: getResourceName(method, fields),
|
|
19
20
|
type: 'worker',
|
|
@@ -26,6 +27,14 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
|
26
27
|
'amqp.destination': fields.destination
|
|
27
28
|
}
|
|
28
29
|
})
|
|
30
|
+
|
|
31
|
+
if (this.config.dsmEnabled && message) {
|
|
32
|
+
const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
|
|
33
|
+
const queue = fields.queue ?? fields.routingKey
|
|
34
|
+
this.tracer.decodeDataStreamsContext(message.properties.headers[CONTEXT_PROPAGATION_KEY])
|
|
35
|
+
this.tracer
|
|
36
|
+
.setCheckpoint(['direction:in', `topic:${queue}`, 'type:rabbitmq'], span, payloadSize)
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
4
4
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
5
5
|
const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
|
|
6
|
+
const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
|
|
7
|
+
const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
|
|
6
8
|
const { getResourceName } = require('./util')
|
|
7
9
|
|
|
8
10
|
class AmqplibProducerPlugin extends ProducerPlugin {
|
|
9
11
|
static get id () { return 'amqplib' }
|
|
10
12
|
static get operation () { return 'command' }
|
|
11
13
|
|
|
12
|
-
start ({ channel = {}, method, fields }) {
|
|
14
|
+
start ({ channel = {}, method, fields, message }) {
|
|
13
15
|
if (method !== 'basic.publish') return
|
|
14
16
|
|
|
15
17
|
const stream = (channel.connection && channel.connection.stream) || {}
|
|
@@ -30,6 +32,17 @@ class AmqplibProducerPlugin extends ProducerPlugin {
|
|
|
30
32
|
fields.headers = fields.headers || {}
|
|
31
33
|
|
|
32
34
|
this.tracer.inject(span, TEXT_MAP, fields.headers)
|
|
35
|
+
|
|
36
|
+
if (this.config.dsmEnabled) {
|
|
37
|
+
const hasRoutingKey = fields.routingKey != null
|
|
38
|
+
const payloadSize = getAmqpMessageSize({ content: message, headers: fields.headers })
|
|
39
|
+
const dataStreamsContext = this.tracer
|
|
40
|
+
.setCheckpoint(
|
|
41
|
+
['direction:out', `exchange:${fields.exchange}`, `has_routing_key:${hasRoutingKey}`, 'type:rabbitmq']
|
|
42
|
+
, span, payloadSize)
|
|
43
|
+
const pathwayCtx = encodePathwayContext(dataStreamsContext)
|
|
44
|
+
fields.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
|
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const {
|
|
3
|
-
CONTEXT_PROPAGATION_KEY
|
|
3
|
+
CONTEXT_PROPAGATION_KEY,
|
|
4
|
+
getSizeOrZero
|
|
4
5
|
} = require('../../../dd-trace/src/datastreams/processor')
|
|
5
6
|
const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway')
|
|
6
7
|
const log = require('../../../dd-trace/src/log')
|
|
7
8
|
const BaseAwsSdkPlugin = require('../base')
|
|
9
|
+
const { storage } = require('../../../datadog-core')
|
|
8
10
|
|
|
9
11
|
class Kinesis extends BaseAwsSdkPlugin {
|
|
10
12
|
static get id () { return 'kinesis' }
|
|
11
13
|
static get peerServicePrecursors () { return ['streamname'] }
|
|
12
14
|
|
|
15
|
+
constructor (...args) {
|
|
16
|
+
super(...args)
|
|
17
|
+
|
|
18
|
+
// TODO(bengl) Find a way to create the response span tags without this WeakMap being populated
|
|
19
|
+
// in the base class
|
|
20
|
+
this.requestTags = new WeakMap()
|
|
21
|
+
|
|
22
|
+
this.addSub('apm:aws:response:start:kinesis', obj => {
|
|
23
|
+
const { request, response } = obj
|
|
24
|
+
const store = storage.getStore()
|
|
25
|
+
const plugin = this
|
|
26
|
+
|
|
27
|
+
// if we have either of these operations, we want to store the streamName param
|
|
28
|
+
// since it is not typically available during get/put records requests
|
|
29
|
+
if (request.operation === 'getShardIterator' || request.operation === 'listShards') {
|
|
30
|
+
this.storeStreamName(request.params, request.operation, store)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (request.operation === 'getRecords') {
|
|
35
|
+
let span
|
|
36
|
+
const responseExtraction = this.responseExtract(request.params, request.operation, response)
|
|
37
|
+
if (responseExtraction && responseExtraction.maybeChildOf) {
|
|
38
|
+
obj.needsFinish = true
|
|
39
|
+
const options = {
|
|
40
|
+
childOf: responseExtraction.maybeChildOf,
|
|
41
|
+
tags: Object.assign(
|
|
42
|
+
{},
|
|
43
|
+
this.requestTags.get(request) || {},
|
|
44
|
+
{ 'span.kind': 'server' }
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
span = plugin.tracer.startSpan('aws.response', options)
|
|
48
|
+
this.enter(span, store)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// get the stream name that should have been stored previously
|
|
52
|
+
const { streamName } = storage.getStore()
|
|
53
|
+
|
|
54
|
+
// extract DSM context after as we might not have a parent-child but may have a DSM context
|
|
55
|
+
this.responseExtractDSMContext(
|
|
56
|
+
request.operation, response, span ?? null, streamName
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
this.addSub('apm:aws:response:finish:kinesis', err => {
|
|
62
|
+
const { span } = storage.getStore()
|
|
63
|
+
this.finish(span, null, err)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
13
67
|
generateTags (params, operation, response) {
|
|
14
68
|
if (!params || !params.StreamName) return {}
|
|
15
69
|
|
|
@@ -20,6 +74,58 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
20
74
|
}
|
|
21
75
|
}
|
|
22
76
|
|
|
77
|
+
storeStreamName (params, operation, store) {
|
|
78
|
+
if (!operation || (operation !== 'getShardIterator' && operation !== 'listShards')) return
|
|
79
|
+
if (!params || !params.StreamName) return
|
|
80
|
+
|
|
81
|
+
const streamName = params.StreamName
|
|
82
|
+
storage.enterWith({ ...store, streamName })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
responseExtract (params, operation, response) {
|
|
86
|
+
if (operation !== 'getRecords') return
|
|
87
|
+
if (params.Limit && params.Limit !== 1) return
|
|
88
|
+
if (!response || !response.Records || !response.Records[0]) return
|
|
89
|
+
|
|
90
|
+
const record = response.Records[0]
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const decodedData = JSON.parse(Buffer.from(record.Data).toString())
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
maybeChildOf: this.tracer.extract('text_map', decodedData._datadog),
|
|
97
|
+
parsedAttributes: decodedData._datadog
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
log.error(e)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
responseExtractDSMContext (operation, response, span, streamName) {
|
|
105
|
+
if (!this.config.dsmEnabled) return
|
|
106
|
+
if (operation !== 'getRecords') return
|
|
107
|
+
if (!response || !response.Records || !response.Records[0]) return
|
|
108
|
+
|
|
109
|
+
// we only want to set the payloadSize on the span if we have one message, not repeatedly
|
|
110
|
+
span = response.Records.length > 1 ? null : span
|
|
111
|
+
|
|
112
|
+
response.Records.forEach(record => {
|
|
113
|
+
const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
parsedAttributes &&
|
|
117
|
+
parsedAttributes._datadog &&
|
|
118
|
+
parsedAttributes._datadog[CONTEXT_PROPAGATION_KEY] &&
|
|
119
|
+
streamName
|
|
120
|
+
) {
|
|
121
|
+
const payloadSize = getSizeOrZero(record.Data)
|
|
122
|
+
this.tracer.decodeDataStreamsContext(Buffer.from(parsedAttributes._datadog[CONTEXT_PROPAGATION_KEY]))
|
|
123
|
+
this.tracer
|
|
124
|
+
.setCheckpoint(['direction:in', `topic:${streamName}`, 'type:kinesis'], span, payloadSize)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
23
129
|
// AWS-SDK will b64 kinesis payloads
|
|
24
130
|
// or will accept an already b64 encoded payload
|
|
25
131
|
// This method handles both
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const scrubChildProcessCmd = require('./scrub-cmd-params')
|
|
5
|
+
|
|
6
|
+
const MAX_ARG_SIZE = 4096 // 4kB
|
|
7
|
+
|
|
8
|
+
function truncateCommand (cmdFields) {
|
|
9
|
+
let size = cmdFields[0].length
|
|
10
|
+
let truncated = false
|
|
11
|
+
for (let i = 1; i < cmdFields.length; i++) {
|
|
12
|
+
if (size >= MAX_ARG_SIZE) {
|
|
13
|
+
truncated = true
|
|
14
|
+
cmdFields[i] = ''
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const argLen = cmdFields[i].length
|
|
19
|
+
if (size < MAX_ARG_SIZE && size + argLen > MAX_ARG_SIZE) {
|
|
20
|
+
cmdFields[i] = cmdFields[i].substring(0, 2)
|
|
21
|
+
truncated = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
size += argLen
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return truncated
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class ChildProcessPlugin extends TracingPlugin {
|
|
31
|
+
static get id () { return 'child_process' }
|
|
32
|
+
static get prefix () { return 'tracing:datadog:child_process:execution' }
|
|
33
|
+
|
|
34
|
+
get tracer () {
|
|
35
|
+
return this._tracer
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
start ({ command, shell }) {
|
|
39
|
+
if (typeof command !== 'string') {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cmdFields = scrubChildProcessCmd(command)
|
|
44
|
+
const truncated = truncateCommand(cmdFields)
|
|
45
|
+
const property = (shell === true) ? 'cmd.shell' : 'cmd.exec'
|
|
46
|
+
|
|
47
|
+
const meta = {
|
|
48
|
+
'component': 'subprocess',
|
|
49
|
+
[property]: (shell === true) ? cmdFields.join(' ') : JSON.stringify(cmdFields)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (truncated) {
|
|
53
|
+
meta['cmd.truncated'] = `${truncated}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.startSpan('command_execution', {
|
|
57
|
+
service: this.config.service,
|
|
58
|
+
resource: (shell === true) ? 'sh' : cmdFields[0],
|
|
59
|
+
type: 'system',
|
|
60
|
+
meta
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
end ({ result, error }) {
|
|
65
|
+
let exitCode
|
|
66
|
+
|
|
67
|
+
if (result !== undefined) {
|
|
68
|
+
exitCode = result?.status || 0
|
|
69
|
+
} else if (error !== undefined) {
|
|
70
|
+
exitCode = error?.status || error?.code || 0
|
|
71
|
+
} else {
|
|
72
|
+
// TracingChannels call start, end synchronously. Later when the promise is resolved then asyncStart asyncEnd.
|
|
73
|
+
// Therefore in the case of calling end with neither result nor error means that they will come in the asyncEnd.
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.activeSpan?.setTag('cmd.exit_code', `${exitCode}`)
|
|
78
|
+
this.activeSpan?.finish()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
error (error) {
|
|
82
|
+
this.addError(error)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
asyncEnd ({ result }) {
|
|
86
|
+
this.activeSpan?.setTag('cmd.exit_code', `${result}`)
|
|
87
|
+
this.activeSpan?.finish()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = ChildProcessPlugin
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const shellParser = require('shell-quote/parse')
|
|
4
|
+
|
|
5
|
+
const ALLOWED_ENV_VARIABLES = ['LD_PRELOAD', 'LD_LIBRARY_PATH', 'PATH']
|
|
6
|
+
const PROCESS_DENYLIST = ['md5']
|
|
7
|
+
|
|
8
|
+
const VARNAMES_REGEX = /\$([\w\d_]*)(?:[^\w\d_]|$)/gmi
|
|
9
|
+
// eslint-disable-next-line max-len
|
|
10
|
+
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$'
|
|
11
|
+
const regexParam = new RegExp(PARAM_PATTERN, 'i')
|
|
12
|
+
const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$'
|
|
13
|
+
const envvarRegex = new RegExp(ENV_PATTERN)
|
|
14
|
+
const REDACTED = '?'
|
|
15
|
+
|
|
16
|
+
function extractVarNames (expression) {
|
|
17
|
+
const varNames = new Set()
|
|
18
|
+
let match
|
|
19
|
+
|
|
20
|
+
while ((match = VARNAMES_REGEX.exec(expression))) {
|
|
21
|
+
varNames.add(match[1])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const varNamesObject = {}
|
|
25
|
+
for (const varName of varNames.keys()) {
|
|
26
|
+
varNamesObject[varName] = `$${varName}`
|
|
27
|
+
}
|
|
28
|
+
return varNamesObject
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getTokensByExpression (expressionTokens) {
|
|
32
|
+
const expressionListTokens = []
|
|
33
|
+
let wipExpressionTokens = []
|
|
34
|
+
let isNewExpression = true
|
|
35
|
+
|
|
36
|
+
expressionTokens.forEach(token => {
|
|
37
|
+
if (isNewExpression) {
|
|
38
|
+
expressionListTokens.push(wipExpressionTokens)
|
|
39
|
+
isNewExpression = false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
wipExpressionTokens.push(token)
|
|
43
|
+
|
|
44
|
+
if (token.op) {
|
|
45
|
+
wipExpressionTokens = []
|
|
46
|
+
isNewExpression = true
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return expressionListTokens
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scrubChildProcessCmd (expression) {
|
|
53
|
+
const varNames = extractVarNames(expression)
|
|
54
|
+
const expressionTokens = shellParser(expression, varNames)
|
|
55
|
+
|
|
56
|
+
const expressionListTokens = getTokensByExpression(expressionTokens)
|
|
57
|
+
|
|
58
|
+
const result = []
|
|
59
|
+
expressionListTokens.forEach((expressionTokens) => {
|
|
60
|
+
let foundBinary = false
|
|
61
|
+
for (let index = 0; index < expressionTokens.length; index++) {
|
|
62
|
+
const token = expressionTokens[index]
|
|
63
|
+
|
|
64
|
+
if (typeof token === 'object') {
|
|
65
|
+
if (token.pattern) {
|
|
66
|
+
result.push(token.pattern)
|
|
67
|
+
} else if (token.op) {
|
|
68
|
+
result.push(token.op)
|
|
69
|
+
} else if (token.comment) {
|
|
70
|
+
result.push(`#${token.comment}`)
|
|
71
|
+
}
|
|
72
|
+
} else if (!foundBinary) {
|
|
73
|
+
if (envvarRegex.test(token)) {
|
|
74
|
+
const envSplit = token.split('=')
|
|
75
|
+
|
|
76
|
+
if (!ALLOWED_ENV_VARIABLES.includes(envSplit[0])) {
|
|
77
|
+
envSplit[1] = REDACTED
|
|
78
|
+
|
|
79
|
+
const newToken = envSplit.join('=')
|
|
80
|
+
expressionTokens[index] = newToken
|
|
81
|
+
|
|
82
|
+
result.push(newToken)
|
|
83
|
+
} else {
|
|
84
|
+
result.push(token)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
foundBinary = true
|
|
88
|
+
result.push(token)
|
|
89
|
+
|
|
90
|
+
if (PROCESS_DENYLIST.includes(token)) {
|
|
91
|
+
for (index++; index < expressionTokens.length; index++) {
|
|
92
|
+
const token = expressionTokens[index]
|
|
93
|
+
|
|
94
|
+
if (token.op) {
|
|
95
|
+
result.push(token.op)
|
|
96
|
+
} else {
|
|
97
|
+
expressionTokens[index] = REDACTED
|
|
98
|
+
result.push(REDACTED)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const paramKeyValue = token.split('=')
|
|
106
|
+
const paramKey = paramKeyValue[0]
|
|
107
|
+
|
|
108
|
+
if (regexParam.test(paramKey)) {
|
|
109
|
+
if (paramKeyValue.length === 1) {
|
|
110
|
+
expressionTokens[index + 1] = REDACTED
|
|
111
|
+
result.push(token)
|
|
112
|
+
} else {
|
|
113
|
+
result.push(`${paramKey}=${REDACTED}`)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
result.push(token)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = scrubChildProcessCmd
|
|
@@ -14,7 +14,8 @@ const {
|
|
|
14
14
|
TEST_ITR_UNSKIPPABLE,
|
|
15
15
|
TEST_ITR_FORCED_RUN,
|
|
16
16
|
TEST_CODE_OWNERS,
|
|
17
|
-
ITR_CORRELATION_ID
|
|
17
|
+
ITR_CORRELATION_ID,
|
|
18
|
+
TEST_SOURCE_FILE
|
|
18
19
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
20
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
20
21
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -47,7 +48,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
47
48
|
hasUnskippableSuites,
|
|
48
49
|
hasForcedToRunSuites
|
|
49
50
|
}) => {
|
|
50
|
-
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.
|
|
51
|
+
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
|
|
51
52
|
addIntelligentTestRunnerSpanTags(
|
|
52
53
|
this.testSessionSpan,
|
|
53
54
|
this.testModuleSpan,
|
|
@@ -71,7 +72,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
71
72
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
72
73
|
finishAllTraceSpans(this.testSessionSpan)
|
|
73
74
|
|
|
74
|
-
this.
|
|
75
|
+
this.libraryConfig = null
|
|
75
76
|
this.tracer._exporter.flush()
|
|
76
77
|
})
|
|
77
78
|
|
|
@@ -102,7 +103,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
102
103
|
}
|
|
103
104
|
})
|
|
104
105
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
105
|
-
if (this.
|
|
106
|
+
if (this.libraryConfig?.isCodeCoverageEnabled) {
|
|
106
107
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
|
|
107
108
|
}
|
|
108
109
|
})
|
|
@@ -114,7 +115,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
114
115
|
})
|
|
115
116
|
|
|
116
117
|
this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
|
|
117
|
-
if (!this.
|
|
118
|
+
if (!this.libraryConfig?.isCodeCoverageEnabled) {
|
|
118
119
|
return
|
|
119
120
|
}
|
|
120
121
|
if (!coverageFiles.length) {
|
|
@@ -122,7 +123,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const relativeCoverageFiles = [...coverageFiles, suiteFile]
|
|
125
|
-
.map(filename => getTestSuitePath(filename, this.
|
|
126
|
+
.map(filename => getTestSuitePath(filename, this.repositoryRoot))
|
|
126
127
|
|
|
127
128
|
this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
|
|
128
129
|
|
|
@@ -136,10 +137,11 @@ class CucumberPlugin extends CiPlugin {
|
|
|
136
137
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
|
|
137
138
|
})
|
|
138
139
|
|
|
139
|
-
this.addSub('ci:cucumber:test:start', ({ testName,
|
|
140
|
+
this.addSub('ci:cucumber:test:start', ({ testName, testFileAbsolutePath, testSourceLine }) => {
|
|
140
141
|
const store = storage.getStore()
|
|
141
|
-
const testSuite = getTestSuitePath(
|
|
142
|
-
const
|
|
142
|
+
const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
|
|
143
|
+
const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
|
|
144
|
+
const testSpan = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine)
|
|
143
145
|
|
|
144
146
|
this.enter(testSpan, store)
|
|
145
147
|
})
|
|
@@ -191,12 +193,15 @@ class CucumberPlugin extends CiPlugin {
|
|
|
191
193
|
})
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
startTestSpan (testName, testSuite, testSourceLine) {
|
|
196
|
+
startTestSpan (testName, testSuite, testSourceFile, testSourceLine) {
|
|
195
197
|
return super.startTestSpan(
|
|
196
198
|
testName,
|
|
197
199
|
testSuite,
|
|
198
200
|
this.testSuiteSpan,
|
|
199
|
-
{
|
|
201
|
+
{
|
|
202
|
+
[TEST_SOURCE_START]: testSourceLine,
|
|
203
|
+
[TEST_SOURCE_FILE]: testSourceFile
|
|
204
|
+
}
|
|
200
205
|
)
|
|
201
206
|
}
|
|
202
207
|
}
|