dd-trace 5.2.0 → 5.3.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 +6 -5
- 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 +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/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 +25 -12
- 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 +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/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 +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 +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/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/datadog-instrumentations/src/child-process.js +0 -29
- package/packages/dd-trace/src/plugins/util/exec.js +0 -34
|
@@ -37,11 +37,16 @@ const testRunFinishCh = channel('ci:jest:test:finish')
|
|
|
37
37
|
const testErrCh = channel('ci:jest:test:err')
|
|
38
38
|
|
|
39
39
|
const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
|
|
40
|
-
const
|
|
40
|
+
const libraryConfigurationCh = channel('ci:jest:library-configuration')
|
|
41
|
+
const knownTestsCh = channel('ci:jest:known-tests')
|
|
41
42
|
|
|
42
43
|
const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
43
44
|
|
|
45
|
+
// Maximum time we'll wait for the tracer to flush
|
|
46
|
+
const FLUSH_TIMEOUT = 10000
|
|
47
|
+
|
|
44
48
|
let skippableSuites = []
|
|
49
|
+
let knownTests = []
|
|
45
50
|
let isCodeCoverageEnabled = false
|
|
46
51
|
let isSuitesSkippingEnabled = false
|
|
47
52
|
let isUserCodeCoverageEnabled = false
|
|
@@ -49,6 +54,11 @@ let isSuitesSkipped = false
|
|
|
49
54
|
let numSkippedSuites = 0
|
|
50
55
|
let hasUnskippableSuites = false
|
|
51
56
|
let hasForcedToRunSuites = false
|
|
57
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
58
|
+
let earlyFlakeDetectionNumRetries = 0
|
|
59
|
+
|
|
60
|
+
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
61
|
+
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
52
62
|
|
|
53
63
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
54
64
|
|
|
@@ -62,6 +72,7 @@ const specStatusToTestStatus = {
|
|
|
62
72
|
|
|
63
73
|
const asyncResources = new WeakMap()
|
|
64
74
|
const originalTestFns = new WeakMap()
|
|
75
|
+
const retriedTestsToNumAttempts = new Map()
|
|
65
76
|
|
|
66
77
|
// based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
|
|
67
78
|
function formatJestError (errors) {
|
|
@@ -90,6 +101,14 @@ function getTestEnvironmentOptions (config) {
|
|
|
90
101
|
return {}
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
function getEfdTestName (testName, numAttempt) {
|
|
105
|
+
return `${EFD_STRING} (#${numAttempt}): ${testName}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function removeEfdTestName (testName) {
|
|
109
|
+
return testName.replace(EFD_TEST_NAME_REGEX, '')
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
94
113
|
return class DatadogEnvironment extends BaseEnvironment {
|
|
95
114
|
constructor (config, context) {
|
|
@@ -97,10 +116,43 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
97
116
|
const rootDir = config.globalConfig ? config.globalConfig.rootDir : config.rootDir
|
|
98
117
|
this.rootDir = rootDir
|
|
99
118
|
this.testSuite = getTestSuitePath(context.testPath, rootDir)
|
|
119
|
+
this.testFileAbsolutePath = context.testPath
|
|
100
120
|
this.nameToParams = {}
|
|
101
121
|
this.global._ddtrace = global._ddtrace
|
|
102
122
|
|
|
103
123
|
this.testEnvironmentOptions = getTestEnvironmentOptions(config)
|
|
124
|
+
|
|
125
|
+
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
|
|
126
|
+
|
|
127
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
128
|
+
earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
|
|
129
|
+
try {
|
|
130
|
+
this.knownTestsForThisSuite = this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// If there has been an error parsing the tests, we'll disable Early Flake Deteciton
|
|
133
|
+
this.isEarlyFlakeDetectionEnabled = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Function that receives a list of known tests for a test service and
|
|
139
|
+
// returns the ones that belong to the current suite
|
|
140
|
+
getKnownTestsForSuite (knownTests) {
|
|
141
|
+
let knownTestsForSuite = knownTests
|
|
142
|
+
// If jest runs in band, the known tests are not serialized, so they're an array.
|
|
143
|
+
if (!Array.isArray(knownTests)) {
|
|
144
|
+
knownTestsForSuite = JSON.parse(knownTestsForSuite)
|
|
145
|
+
}
|
|
146
|
+
return knownTestsForSuite
|
|
147
|
+
.filter(test => test.includes(this.testSuite))
|
|
148
|
+
.map(test => test.replace(`jest.${this.testSuite}.`, '').trim())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add the `add_test` event we don't have the test object yet, so
|
|
152
|
+
// we use its describe block to get the full name
|
|
153
|
+
getTestNameFromAddTestEvent (event, state) {
|
|
154
|
+
const describeSuffix = getJestTestName(state.currentDescribeBlock)
|
|
155
|
+
return removeEfdTestName(`${describeSuffix} ${event.testName}`).trim()
|
|
104
156
|
}
|
|
105
157
|
|
|
106
158
|
async handleTestEvent (event, state) {
|
|
@@ -124,23 +176,56 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
124
176
|
}
|
|
125
177
|
}
|
|
126
178
|
if (event.name === 'test_start') {
|
|
179
|
+
let isNewTest = false
|
|
180
|
+
let numEfdRetry = null
|
|
127
181
|
const testParameters = getTestParametersString(this.nameToParams, event.test.name)
|
|
128
182
|
// Async resource for this test is created here
|
|
129
183
|
// It is used later on by the test_done handler
|
|
130
184
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
131
185
|
asyncResources.set(event.test, asyncResource)
|
|
186
|
+
const testName = getJestTestName(event.test)
|
|
187
|
+
|
|
188
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
189
|
+
const originalTestName = removeEfdTestName(testName)
|
|
190
|
+
isNewTest = retriedTestsToNumAttempts.has(originalTestName)
|
|
191
|
+
if (isNewTest) {
|
|
192
|
+
numEfdRetry = retriedTestsToNumAttempts.get(originalTestName)
|
|
193
|
+
retriedTestsToNumAttempts.set(originalTestName, numEfdRetry + 1)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
132
197
|
asyncResource.runInAsyncScope(() => {
|
|
133
198
|
testStartCh.publish({
|
|
134
|
-
name:
|
|
199
|
+
name: removeEfdTestName(testName),
|
|
135
200
|
suite: this.testSuite,
|
|
201
|
+
testFileAbsolutePath: this.testFileAbsolutePath,
|
|
136
202
|
runner: 'jest-circus',
|
|
137
203
|
testParameters,
|
|
138
|
-
frameworkVersion: jestVersion
|
|
204
|
+
frameworkVersion: jestVersion,
|
|
205
|
+
isNew: isNewTest,
|
|
206
|
+
isEfdRetry: numEfdRetry > 0
|
|
139
207
|
})
|
|
140
208
|
originalTestFns.set(event.test, event.test.fn)
|
|
141
209
|
event.test.fn = asyncResource.bind(event.test.fn)
|
|
142
210
|
})
|
|
143
211
|
}
|
|
212
|
+
if (event.name === 'add_test') {
|
|
213
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
214
|
+
const testName = this.getTestNameFromAddTestEvent(event, state)
|
|
215
|
+
const isNew = !this.knownTestsForThisSuite?.includes(testName)
|
|
216
|
+
const isSkipped = event.mode === 'todo' || event.mode === 'skip'
|
|
217
|
+
if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testName)) {
|
|
218
|
+
retriedTestsToNumAttempts.set(testName, 0)
|
|
219
|
+
for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
|
|
220
|
+
if (this.global.test) {
|
|
221
|
+
this.global.test(getEfdTestName(event.testName, retryIndex), event.fn, event.timeout)
|
|
222
|
+
} else {
|
|
223
|
+
log.error('Early flake detection could not retry test because global.test is undefined')
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
144
229
|
if (event.name === 'test_done') {
|
|
145
230
|
const asyncResource = asyncResources.get(event.test)
|
|
146
231
|
asyncResource.runInAsyncScope(() => {
|
|
@@ -164,6 +249,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
164
249
|
testSkippedCh.publish({
|
|
165
250
|
name: getJestTestName(event.test),
|
|
166
251
|
suite: this.testSuite,
|
|
252
|
+
testFileAbsolutePath: this.testFileAbsolutePath,
|
|
167
253
|
runner: 'jest-circus',
|
|
168
254
|
frameworkVersion: jestVersion,
|
|
169
255
|
testStartLine: getTestLineStart(event.test.asyncError, this.testSuite)
|
|
@@ -206,7 +292,7 @@ addHook({
|
|
|
206
292
|
}
|
|
207
293
|
// TODO: could we get the rootDir from each test?
|
|
208
294
|
const [test] = shardedTests
|
|
209
|
-
const rootDir = test
|
|
295
|
+
const rootDir = test?.context?.config?.rootDir
|
|
210
296
|
|
|
211
297
|
const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
|
|
212
298
|
|
|
@@ -234,24 +320,45 @@ function cliWrapper (cli, jestVersion) {
|
|
|
234
320
|
const configurationPromise = new Promise((resolve) => {
|
|
235
321
|
onDone = resolve
|
|
236
322
|
})
|
|
237
|
-
if (!
|
|
323
|
+
if (!libraryConfigurationCh.hasSubscribers) {
|
|
238
324
|
return runCLI.apply(this, arguments)
|
|
239
325
|
}
|
|
240
326
|
|
|
241
327
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
242
|
-
|
|
328
|
+
libraryConfigurationCh.publish({ onDone })
|
|
243
329
|
})
|
|
244
330
|
|
|
245
331
|
try {
|
|
246
|
-
const { err,
|
|
332
|
+
const { err, libraryConfig } = await configurationPromise
|
|
247
333
|
if (!err) {
|
|
248
|
-
isCodeCoverageEnabled =
|
|
249
|
-
isSuitesSkippingEnabled =
|
|
334
|
+
isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
|
|
335
|
+
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
336
|
+
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
337
|
+
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
250
338
|
}
|
|
251
339
|
} catch (err) {
|
|
252
340
|
log.error(err)
|
|
253
341
|
}
|
|
254
342
|
|
|
343
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
344
|
+
const knownTestsPromise = new Promise((resolve) => {
|
|
345
|
+
onDone = resolve
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
349
|
+
knownTestsCh.publish({ onDone })
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const { err, knownTests: receivedKnownTests } = await knownTestsPromise
|
|
354
|
+
if (!err) {
|
|
355
|
+
knownTests = receivedKnownTests
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
log.error(err)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
255
362
|
if (isSuitesSkippingEnabled) {
|
|
256
363
|
const skippableSuitesPromise = new Promise((resolve) => {
|
|
257
364
|
onDone = resolve
|
|
@@ -311,6 +418,21 @@ function cliWrapper (cli, jestVersion) {
|
|
|
311
418
|
status = 'fail'
|
|
312
419
|
error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
313
420
|
}
|
|
421
|
+
let timeoutId
|
|
422
|
+
|
|
423
|
+
// Pass the resolve callback to defer it to DC listener
|
|
424
|
+
const flushPromise = new Promise((resolve) => {
|
|
425
|
+
onDone = () => {
|
|
426
|
+
clearTimeout(timeoutId)
|
|
427
|
+
resolve()
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
432
|
+
timeoutId = setTimeout(() => {
|
|
433
|
+
resolve('timeout')
|
|
434
|
+
}, FLUSH_TIMEOUT).unref()
|
|
435
|
+
})
|
|
314
436
|
|
|
315
437
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
316
438
|
testSessionFinishCh.publish({
|
|
@@ -322,9 +444,16 @@ function cliWrapper (cli, jestVersion) {
|
|
|
322
444
|
numSkippedSuites,
|
|
323
445
|
hasUnskippableSuites,
|
|
324
446
|
hasForcedToRunSuites,
|
|
325
|
-
error
|
|
447
|
+
error,
|
|
448
|
+
isEarlyFlakeDetectionEnabled,
|
|
449
|
+
onDone
|
|
326
450
|
})
|
|
327
451
|
})
|
|
452
|
+
const waitingResult = await Promise.race([flushPromise, timeoutPromise])
|
|
453
|
+
|
|
454
|
+
if (waitingResult === 'timeout') {
|
|
455
|
+
log.error('Timeout waiting for the tracer to flush')
|
|
456
|
+
}
|
|
328
457
|
|
|
329
458
|
numSkippedSuites = 0
|
|
330
459
|
|
|
@@ -438,10 +567,15 @@ function configureTestEnvironment (readConfigsResult) {
|
|
|
438
567
|
// because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
|
|
439
568
|
configs.forEach(config => {
|
|
440
569
|
config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
|
|
570
|
+
config.testEnvironmentOptions._ddKnownTests = knownTests
|
|
441
571
|
})
|
|
442
572
|
|
|
443
573
|
isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
|
|
444
574
|
|
|
575
|
+
if (readConfigsResult.globalConfig.forceExit) {
|
|
576
|
+
log.warn("Jest's '--forceExit' flag has been passed. This may cause loss of data.")
|
|
577
|
+
}
|
|
578
|
+
|
|
445
579
|
if (isCodeCoverageEnabled) {
|
|
446
580
|
const globalConfig = {
|
|
447
581
|
...readConfigsResult.globalConfig,
|
|
@@ -498,6 +632,9 @@ addHook({
|
|
|
498
632
|
_ddForcedToRun,
|
|
499
633
|
_ddUnskippable,
|
|
500
634
|
_ddItrCorrelationId,
|
|
635
|
+
_ddKnownTests,
|
|
636
|
+
_ddIsEarlyFlakeDetectionEnabled,
|
|
637
|
+
_ddEarlyFlakeDetectionNumRetries,
|
|
501
638
|
...restOfTestEnvironmentOptions
|
|
502
639
|
} = testEnvironmentOptions
|
|
503
640
|
|
|
@@ -20,7 +20,7 @@ const skipCh = channel('ci:mocha:test:skip')
|
|
|
20
20
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
21
21
|
const parameterizedTestCh = channel('ci:mocha:test:parameterize')
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
24
24
|
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
25
25
|
|
|
26
26
|
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
@@ -384,7 +384,7 @@ addHook({
|
|
|
384
384
|
return run.apply(this, arguments)
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
if (!
|
|
387
|
+
if (!libraryConfigurationCh.hasSubscribers || this.isWorker) {
|
|
388
388
|
if (this.isWorker) {
|
|
389
389
|
isWorker = true
|
|
390
390
|
}
|
|
@@ -439,7 +439,7 @@ addHook({
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
mochaRunAsyncResource.runInAsyncScope(() => {
|
|
442
|
-
|
|
442
|
+
libraryConfigurationCh.publish({
|
|
443
443
|
onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
|
|
444
444
|
})
|
|
445
445
|
})
|
|
@@ -79,21 +79,26 @@ addHook({
|
|
|
79
79
|
})
|
|
80
80
|
|
|
81
81
|
let callbackWrapped = false
|
|
82
|
-
const lastArgumentIndex = arguments.length - 1
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
|
|
87
|
-
return function () {
|
|
88
|
-
finish()
|
|
83
|
+
const wrapCallbackIfExist = (args) => {
|
|
84
|
+
const lastArgumentIndex = args.length - 1
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
if (typeof args[lastArgumentIndex] === 'function') {
|
|
87
|
+
// is a callback, wrap it to execute finish()
|
|
88
|
+
shimmer.wrap(args, lastArgumentIndex, originalCb => {
|
|
89
|
+
return function () {
|
|
90
|
+
finish()
|
|
91
|
+
|
|
92
|
+
return originalCb.apply(this, arguments)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
callbackWrapped = true
|
|
97
|
+
}
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
wrapCallbackIfExist(arguments)
|
|
101
|
+
|
|
97
102
|
return asyncResource.runInAsyncScope(() => {
|
|
98
103
|
startCh.publish({
|
|
99
104
|
filters,
|
|
@@ -106,8 +111,16 @@ addHook({
|
|
|
106
111
|
if (!callbackWrapped) {
|
|
107
112
|
shimmer.wrap(res, 'exec', originalExec => {
|
|
108
113
|
return function wrappedExec () {
|
|
114
|
+
if (!callbackWrapped) {
|
|
115
|
+
wrapCallbackIfExist(arguments)
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
const execResult = originalExec.apply(this, arguments)
|
|
110
119
|
|
|
120
|
+
if (callbackWrapped || typeof execResult?.then !== 'function') {
|
|
121
|
+
return execResult
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
// wrap them method, wrap resolve and reject methods
|
|
112
125
|
shimmer.wrap(execResult, 'then', originalThen => {
|
|
113
126
|
return function wrappedThen () {
|
|
@@ -290,9 +290,23 @@ addHook({
|
|
|
290
290
|
shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
|
|
291
291
|
return async function wrappedJson () {
|
|
292
292
|
const body = await originalMethod.apply(this, arguments)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
|
|
294
|
+
bodyParsedChannel.publish({ body })
|
|
295
|
+
|
|
296
|
+
return body
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
shimmer.wrap(request.NextRequest.prototype, 'formData', function (originalFormData) {
|
|
301
|
+
return async function wrappedFormData () {
|
|
302
|
+
const body = await originalFormData.apply(this, arguments)
|
|
303
|
+
|
|
304
|
+
let normalizedBody = body
|
|
305
|
+
if (typeof body.entries === 'function') {
|
|
306
|
+
normalizedBody = Object.fromEntries(body.entries())
|
|
307
|
+
}
|
|
308
|
+
bodyParsedChannel.publish({ body: normalizedBody })
|
|
309
|
+
|
|
296
310
|
return body
|
|
297
311
|
}
|
|
298
312
|
})
|
|
@@ -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
|