dd-trace 4.41.0 → 4.43.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.
Files changed (93) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/ext/exporters.d.ts +1 -1
  3. package/index.d.ts +105 -37
  4. package/init.js +40 -1
  5. package/initialize.mjs +8 -5
  6. package/package.json +29 -29
  7. package/packages/datadog-core/src/storage/index.js +1 -10
  8. package/packages/datadog-esbuild/index.js +5 -1
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  10. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +76 -34
  12. package/packages/datadog-instrumentations/src/fs.js +1 -1
  13. package/packages/datadog-instrumentations/src/hapi.js +1 -1
  14. package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
  17. package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
  18. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  19. package/packages/datadog-instrumentations/src/jest.js +17 -2
  20. package/packages/datadog-instrumentations/src/kafkajs.js +1 -1
  21. package/packages/datadog-instrumentations/src/ldapjs.js +2 -2
  22. package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
  25. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  26. package/packages/datadog-instrumentations/src/next.js +1 -1
  27. package/packages/datadog-instrumentations/src/pg.js +2 -2
  28. package/packages/datadog-instrumentations/src/playwright.js +47 -33
  29. package/packages/datadog-instrumentations/src/restify.js +1 -1
  30. package/packages/datadog-instrumentations/src/vitest.js +349 -0
  31. package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
  32. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  34. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
  35. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
  36. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  37. package/packages/datadog-plugin-child_process/src/index.js +1 -1
  38. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -4
  39. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  40. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +79 -42
  41. package/packages/datadog-plugin-cypress/src/plugin.js +4 -3
  42. package/packages/datadog-plugin-fs/src/index.js +1 -1
  43. package/packages/datadog-plugin-jest/src/index.js +7 -1
  44. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -1
  45. package/packages/datadog-plugin-mocha/src/index.js +25 -4
  46. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -1
  47. package/packages/datadog-plugin-openai/src/index.js +57 -35
  48. package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  50. package/packages/datadog-plugin-sharedb/src/index.js +1 -1
  51. package/packages/datadog-plugin-vitest/src/index.js +167 -0
  52. package/packages/dd-trace/src/analytics_sampler.js +1 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +1 -1
  54. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
  55. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  57. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +3 -1
  58. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
  59. package/packages/dd-trace/src/appsec/index.js +4 -4
  60. package/packages/dd-trace/src/appsec/passport.js +1 -1
  61. package/packages/dd-trace/src/appsec/rasp.js +32 -5
  62. package/packages/dd-trace/src/appsec/recommended.json +208 -3
  63. package/packages/dd-trace/src/appsec/reporter.js +60 -20
  64. package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
  65. package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
  66. package/packages/dd-trace/src/appsec/standalone.js +130 -0
  67. package/packages/dd-trace/src/appsec/telemetry.js +33 -1
  68. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  69. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -3
  70. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  71. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  72. package/packages/dd-trace/src/config.js +136 -63
  73. package/packages/dd-trace/src/constants.js +3 -1
  74. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  75. package/packages/dd-trace/src/exporters/agent/index.js +2 -2
  76. package/packages/dd-trace/src/format.js +1 -0
  77. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  78. package/packages/dd-trace/src/opentelemetry/tracer.js +6 -0
  79. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
  80. package/packages/dd-trace/src/opentracing/span.js +4 -1
  81. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  82. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
  83. package/packages/dd-trace/src/plugins/index.js +2 -0
  84. package/packages/dd-trace/src/plugins/util/test.js +5 -1
  85. package/packages/dd-trace/src/priority_sampler.js +2 -5
  86. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  87. package/packages/dd-trace/src/proxy.js +3 -1
  88. package/packages/dd-trace/src/rate_limiter.js +2 -2
  89. package/packages/dd-trace/src/span_stats.js +4 -3
  90. package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
  91. package/packages/dd-trace/src/tracer.js +2 -2
  92. package/packages/dd-trace/src/util.js +6 -1
  93. package/packages/datadog-core/src/storage/async_hooks.js +0 -49
@@ -2,7 +2,7 @@ const semver = require('semver')
2
2
 
3
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { parseAnnotations, getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
5
+ const { parseAnnotations, getTestSuitePath, NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test')
6
6
  const log = require('../../dd-trace/src/log')
7
7
 
8
8
  const testStartCh = channel('ci:playwright:test:start')
@@ -21,6 +21,7 @@ const testToAr = new WeakMap()
21
21
  const testSuiteToAr = new Map()
22
22
  const testSuiteToTestStatuses = new Map()
23
23
  const testSuiteToErrors = new Map()
24
+ const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
24
25
 
25
26
  let applyRepeatEachIndex = null
26
27
 
@@ -35,6 +36,7 @@ const STATUS_TO_TEST_STATUS = {
35
36
 
36
37
  let remainingTestsByFile = {}
37
38
  let isEarlyFlakeDetectionEnabled = false
39
+ let isFlakyTestRetriesEnabled = false
38
40
  let earlyFlakeDetectionNumRetries = 0
39
41
  let knownTests = {}
40
42
  let rootDir = ''
@@ -196,6 +198,31 @@ function getTestSuiteError (testSuiteAbsolutePath) {
196
198
  return new Error(`${errors.length} errors in this test suite:\n${errors.map(e => e.message).join('\n------\n')}`)
197
199
  }
198
200
 
201
+ function getTestByTestId (dispatcher, testId) {
202
+ if (dispatcher._testById) {
203
+ return dispatcher._testById.get(testId)?.test
204
+ }
205
+ const allTests = dispatcher._allTests || dispatcher._ddAllTests
206
+ if (allTests) {
207
+ return allTests.find(({ id }) => id === testId)
208
+ }
209
+ }
210
+
211
+ function getChannelPromise (channelToPublishTo) {
212
+ return new Promise(resolve => {
213
+ testSessionAsyncResource.runInAsyncScope(() => {
214
+ channelToPublishTo.publish({ onDone: resolve })
215
+ })
216
+ })
217
+ }
218
+ // eslint-disable-next-line
219
+ // Inspired by https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/reporters/base.ts#L293
220
+ // We can't use test.outcome() directly because it's set on follow up handlers:
221
+ // our `testEndHandler` is called before the outcome is set.
222
+ function testWillRetry (test, testStatus) {
223
+ return testStatus === 'fail' && test.results.length <= test.retries
224
+ }
225
+
199
226
  function testBeginHandler (test, browserName) {
200
227
  const {
201
228
  _requireFile: testSuiteAbsolutePath,
@@ -249,7 +276,8 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
249
276
  testAsyncResource.runInAsyncScope(() => {
250
277
  testFinishCh.publish({
251
278
  testStatus,
252
- steps: testResult.steps,
279
+ steps: testResult?.steps || [],
280
+ isRetry: testResult?.retry > 0,
253
281
  error,
254
282
  extraTags: annotationTags,
255
283
  isNew: test._ddIsNew,
@@ -267,8 +295,10 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
267
295
  addErrorToTestSuite(testSuiteAbsolutePath, error)
268
296
  }
269
297
 
270
- remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
271
- .filter(currentTest => currentTest !== test)
298
+ if (!testWillRetry(test, testStatus)) {
299
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
300
+ .filter(currentTest => currentTest !== test)
301
+ }
272
302
 
273
303
  // Last test, we finish the suite
274
304
  if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
@@ -335,16 +365,6 @@ function dispatcherHook (dispatcherExport) {
335
365
  return dispatcherExport
336
366
  }
337
367
 
338
- function getTestByTestId (dispatcher, testId) {
339
- if (dispatcher._testById) {
340
- return dispatcher._testById.get(testId)?.test
341
- }
342
- const allTests = dispatcher._allTests || dispatcher._ddAllTests
343
- if (allTests) {
344
- return allTests.find(({ id }) => id === testId)
345
- }
346
- }
347
-
348
368
  function dispatcherHookNew (dispatcherExport, runWrapper) {
349
369
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
350
370
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
@@ -373,8 +393,6 @@ function runnerHook (runnerExport, playwrightVersion) {
373
393
  shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
374
394
  let onDone
375
395
 
376
- const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
377
-
378
396
  rootDir = getRootDir(this)
379
397
 
380
398
  const processArgv = process.argv.slice(2).join(' ')
@@ -383,46 +401,42 @@ function runnerHook (runnerExport, playwrightVersion) {
383
401
  testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
384
402
  })
385
403
 
386
- const configurationPromise = new Promise((resolve) => {
387
- onDone = resolve
388
- })
389
-
390
- testSessionAsyncResource.runInAsyncScope(() => {
391
- libraryConfigurationCh.publish({ onDone })
392
- })
393
-
394
404
  try {
395
- const { err, libraryConfig } = await configurationPromise
405
+ const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
396
406
  if (!err) {
397
407
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
398
408
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
409
+ isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
399
410
  }
400
411
  } catch (e) {
412
+ isEarlyFlakeDetectionEnabled = false
401
413
  log.error(e)
402
414
  }
403
415
 
404
416
  if (isEarlyFlakeDetectionEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) {
405
- const knownTestsPromise = new Promise((resolve) => {
406
- onDone = resolve
407
- })
408
- testSessionAsyncResource.runInAsyncScope(() => {
409
- knownTestsCh.publish({ onDone })
410
- })
411
-
412
417
  try {
413
- const { err, knownTests: receivedKnownTests } = await knownTestsPromise
418
+ const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
414
419
  if (!err) {
415
420
  knownTests = receivedKnownTests
416
421
  } else {
417
422
  isEarlyFlakeDetectionEnabled = false
418
423
  }
419
424
  } catch (err) {
425
+ isEarlyFlakeDetectionEnabled = false
420
426
  log.error(err)
421
427
  }
422
428
  }
423
429
 
424
430
  const projects = getProjectsFromRunner(this)
425
431
 
432
+ if (isFlakyTestRetriesEnabled) {
433
+ projects.forEach(project => {
434
+ if (project.retries === 0) { // Only if it hasn't been set by the user
435
+ project.retries = NUM_FAILED_TEST_RETRIES
436
+ }
437
+ })
438
+ }
439
+
426
440
  const runAllTestsReturn = await runAllTests.apply(this, arguments)
427
441
 
428
442
  Object.values(remainingTestsByFile).forEach(tests => {
@@ -51,7 +51,7 @@ function wrapFn (fn) {
51
51
 
52
52
  try {
53
53
  const result = fn.apply(this, arguments)
54
- if (result && typeof result === 'object' && typeof result.then === 'function') {
54
+ if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
55
55
  return result.then(function () {
56
56
  nextChannel.publish({ req })
57
57
  finishChannel.publish({ req })
@@ -0,0 +1,349 @@
1
+ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+ const { NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test')
4
+
5
+ // test hooks
6
+ const testStartCh = channel('ci:vitest:test:start')
7
+ const testFinishTimeCh = channel('ci:vitest:test:finish-time')
8
+ const testPassCh = channel('ci:vitest:test:pass')
9
+ const testErrorCh = channel('ci:vitest:test:error')
10
+ const testSkipCh = channel('ci:vitest:test:skip')
11
+
12
+ // test suite hooks
13
+ const testSuiteStartCh = channel('ci:vitest:test-suite:start')
14
+ const testSuiteFinishCh = channel('ci:vitest:test-suite:finish')
15
+ const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
16
+
17
+ // test session hooks
18
+ const testSessionStartCh = channel('ci:vitest:session:start')
19
+ const testSessionFinishCh = channel('ci:vitest:session:finish')
20
+ const libraryConfigurationCh = channel('ci:vitest:library-configuration')
21
+
22
+ const taskToAsync = new WeakMap()
23
+
24
+ const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
25
+
26
+ function isReporterPackage (vitestPackage) {
27
+ return vitestPackage.B?.name === 'BaseSequencer'
28
+ }
29
+
30
+ // from 2.0.0
31
+ function isReporterPackageNew (vitestPackage) {
32
+ return vitestPackage.e?.name === 'BaseSequencer'
33
+ }
34
+
35
+ function getChannelPromise (channelToPublishTo) {
36
+ return new Promise(resolve => {
37
+ sessionAsyncResource.runInAsyncScope(() => {
38
+ channelToPublishTo.publish({ onDone: resolve })
39
+ })
40
+ })
41
+ }
42
+
43
+ function getSessionStatus (state) {
44
+ if (state.getCountOfFailedTests() > 0) {
45
+ return 'fail'
46
+ }
47
+ if (state.pathsSet.size === 0) {
48
+ return 'skip'
49
+ }
50
+ return 'pass'
51
+ }
52
+
53
+ // eslint-disable-next-line
54
+ // From https://github.com/vitest-dev/vitest/blob/51c04e2f44d91322b334f8ccbcdb368facc3f8ec/packages/runner/src/run.ts#L243-L250
55
+ function getVitestTestStatus (test, retryCount) {
56
+ if (test.result.state !== 'fail') {
57
+ if (!test.repeats) {
58
+ return 'pass'
59
+ } else if (test.repeats && (test.retry ?? 0) === retryCount) {
60
+ return 'pass'
61
+ }
62
+ }
63
+ return 'fail'
64
+ }
65
+
66
+ function getTypeTasks (fileTasks, type = 'test') {
67
+ const typeTasks = []
68
+
69
+ function getTasks (tasks) {
70
+ for (const task of tasks) {
71
+ if (task.type === type) {
72
+ typeTasks.push(task)
73
+ } else if (task.tasks) {
74
+ getTasks(task.tasks)
75
+ }
76
+ }
77
+ }
78
+
79
+ getTasks(fileTasks)
80
+
81
+ return typeTasks
82
+ }
83
+
84
+ function getTestName (task) {
85
+ let testName = task.name
86
+ let currentTask = task.suite
87
+
88
+ while (currentTask) {
89
+ if (currentTask.name) {
90
+ testName = `${currentTask.name} ${testName}`
91
+ }
92
+ currentTask = currentTask.suite
93
+ }
94
+
95
+ return testName
96
+ }
97
+
98
+ function getSortWrapper (sort) {
99
+ return async function () {
100
+ if (!testSessionFinishCh.hasSubscribers) {
101
+ return sort.apply(this, arguments)
102
+ }
103
+ // There isn't any other async function that we seem to be able to hook into
104
+ // So we will use the sort from BaseSequencer. This means that a custom sequencer
105
+ // will not work. This will be a known limitation.
106
+ let isFlakyTestRetriesEnabled = false
107
+
108
+ try {
109
+ const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
110
+ if (!err) {
111
+ isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
112
+ }
113
+ } catch (e) {
114
+ isFlakyTestRetriesEnabled = false
115
+ }
116
+ if (isFlakyTestRetriesEnabled && !this.ctx.config.retry) {
117
+ this.ctx.config.retry = NUM_FAILED_TEST_RETRIES
118
+ }
119
+
120
+ shimmer.wrap(this.ctx, 'exit', exit => async function () {
121
+ let onFinish
122
+
123
+ const flushPromise = new Promise(resolve => {
124
+ onFinish = resolve
125
+ })
126
+ const failedSuites = this.state.getFailedFilepaths()
127
+ let error
128
+ if (failedSuites.length) {
129
+ error = new Error(`Test suites failed: ${failedSuites.length}.`)
130
+ }
131
+
132
+ sessionAsyncResource.runInAsyncScope(() => {
133
+ testSessionFinishCh.publish({
134
+ status: getSessionStatus(this.state),
135
+ onFinish,
136
+ error
137
+ })
138
+ })
139
+
140
+ await flushPromise
141
+
142
+ return exit.apply(this, arguments)
143
+ })
144
+
145
+ return sort.apply(this, arguments)
146
+ }
147
+ }
148
+
149
+ addHook({
150
+ name: 'vitest',
151
+ versions: ['>=1.6.0'],
152
+ file: 'dist/runners.js'
153
+ }, (vitestPackage) => {
154
+ const { VitestTestRunner } = vitestPackage
155
+ // test start (only tests that are not marked as skip or todo)
156
+ shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
157
+ if (!testStartCh.hasSubscribers) {
158
+ return onBeforeTryTask.apply(this, arguments)
159
+ }
160
+ const { retry: numAttempt } = retryInfo
161
+ // We finish the previous test here because we know it has failed already
162
+ if (numAttempt > 0) {
163
+ const asyncResource = taskToAsync.get(task)
164
+ const testError = task.result?.errors?.[0]
165
+ if (asyncResource) {
166
+ asyncResource.runInAsyncScope(() => {
167
+ testErrorCh.publish({ error: testError })
168
+ })
169
+ }
170
+ }
171
+
172
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
173
+ taskToAsync.set(task, asyncResource)
174
+
175
+ asyncResource.runInAsyncScope(() => {
176
+ testStartCh.publish({
177
+ testName: getTestName(task),
178
+ testSuiteAbsolutePath: task.file.filepath,
179
+ isRetry: numAttempt > 0
180
+ })
181
+ })
182
+ return onBeforeTryTask.apply(this, arguments)
183
+ })
184
+
185
+ // test finish (only passed tests)
186
+ shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
187
+ async function (task, { retry: retryCount }) {
188
+ if (!testFinishTimeCh.hasSubscribers) {
189
+ return onAfterTryTask.apply(this, arguments)
190
+ }
191
+ const result = await onAfterTryTask.apply(this, arguments)
192
+
193
+ const status = getVitestTestStatus(task, retryCount)
194
+ const asyncResource = taskToAsync.get(task)
195
+
196
+ if (asyncResource) {
197
+ // We don't finish here because the test might fail in a later hook
198
+ asyncResource.runInAsyncScope(() => {
199
+ testFinishTimeCh.publish({ status, task })
200
+ })
201
+ }
202
+
203
+ return result
204
+ })
205
+
206
+ return vitestPackage
207
+ })
208
+
209
+ addHook({
210
+ name: 'vitest',
211
+ versions: ['>=2.0.0'],
212
+ filePattern: 'dist/vendor/index.*'
213
+ }, (vitestPackage) => {
214
+ // there are multiple index* files so we have to check the exported values
215
+ if (isReporterPackageNew(vitestPackage)) {
216
+ shimmer.wrap(vitestPackage.e.prototype, 'sort', getSortWrapper)
217
+ }
218
+
219
+ return vitestPackage
220
+ })
221
+
222
+ addHook({
223
+ name: 'vitest',
224
+ versions: ['>=1.6.0'],
225
+ filePattern: 'dist/vendor/index.*'
226
+ }, (vitestPackage) => {
227
+ // there are multiple index* files so we have to check the exported values
228
+ if (isReporterPackage(vitestPackage)) {
229
+ shimmer.wrap(vitestPackage.B.prototype, 'sort', getSortWrapper)
230
+ }
231
+
232
+ return vitestPackage
233
+ })
234
+
235
+ // Can't specify file because compiled vitest includes hashes in their files
236
+ addHook({
237
+ name: 'vitest',
238
+ versions: ['>=1.6.0'],
239
+ filePattern: 'dist/vendor/cac.*'
240
+ }, (vitestPackage, frameworkVersion) => {
241
+ shimmer.wrap(vitestPackage, 'c', oldCreateCli => function () {
242
+ if (!testSessionStartCh.hasSubscribers) {
243
+ return oldCreateCli.apply(this, arguments)
244
+ }
245
+ sessionAsyncResource.runInAsyncScope(() => {
246
+ const processArgv = process.argv.slice(2).join(' ')
247
+ testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion })
248
+ })
249
+ return oldCreateCli.apply(this, arguments)
250
+ })
251
+
252
+ return vitestPackage
253
+ })
254
+
255
+ // test suite start and finish
256
+ // only relevant for workers
257
+ addHook({
258
+ name: '@vitest/runner',
259
+ versions: ['>=1.6.0'],
260
+ file: 'dist/index.js'
261
+ }, vitestPackage => {
262
+ shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPath) {
263
+ let testSuiteError = null
264
+ if (!testSuiteStartCh.hasSubscribers) {
265
+ return startTests.apply(this, arguments)
266
+ }
267
+
268
+ const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
269
+ testSuiteAsyncResource.runInAsyncScope(() => {
270
+ testSuiteStartCh.publish(testPath[0])
271
+ })
272
+ const startTestsResponse = await startTests.apply(this, arguments)
273
+
274
+ let onFinish = null
275
+ const onFinishPromise = new Promise(resolve => {
276
+ onFinish = resolve
277
+ })
278
+
279
+ const testTasks = getTypeTasks(startTestsResponse[0].tasks)
280
+
281
+ // Only one test task per test, even if there are retries
282
+ testTasks.forEach(task => {
283
+ const testAsyncResource = taskToAsync.get(task)
284
+ const { result } = task
285
+
286
+ if (result) {
287
+ const { state, duration, errors } = result
288
+ if (state === 'skip') { // programmatic skip
289
+ testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
290
+ } else if (state === 'pass') {
291
+ if (testAsyncResource) {
292
+ testAsyncResource.runInAsyncScope(() => {
293
+ testPassCh.publish({ task })
294
+ })
295
+ }
296
+ } else if (state === 'fail') {
297
+ // If it's failing, we have no accurate finish time, so we have to use `duration`
298
+ let testError
299
+
300
+ if (errors?.length) {
301
+ testError = errors[0]
302
+ }
303
+
304
+ if (testAsyncResource) {
305
+ const isRetry = task.result?.retryCount > 0
306
+ // `duration` is the duration of all the retries, so it can't be used if there are retries
307
+ testAsyncResource.runInAsyncScope(() => {
308
+ testErrorCh.publish({ duration: !isRetry ? duration : undefined, error: testError })
309
+ })
310
+ }
311
+ if (errors?.length) {
312
+ testSuiteError = testError // we store the error to bubble it up to the suite
313
+ }
314
+ }
315
+ } else { // test.skip or test.todo
316
+ testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
317
+ }
318
+ })
319
+
320
+ const testSuiteResult = startTestsResponse[0].result
321
+
322
+ if (testSuiteResult.errors?.length) { // Errors from root level hooks
323
+ testSuiteError = testSuiteResult.errors[0]
324
+ } else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
325
+ const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
326
+ const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
327
+ if (failedSuites.length && failedSuites[0].result?.errors?.length) {
328
+ testSuiteError = failedSuites[0].result.errors[0]
329
+ }
330
+ }
331
+
332
+ if (testSuiteError) {
333
+ testSuiteAsyncResource.runInAsyncScope(() => {
334
+ testSuiteErrorCh.publish({ error: testSuiteError })
335
+ })
336
+ }
337
+
338
+ testSuiteAsyncResource.runInAsyncScope(() => {
339
+ testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish })
340
+ })
341
+
342
+ // TODO: fix too frequent flushes
343
+ await onFinishPromise
344
+
345
+ return startTestsResponse
346
+ })
347
+
348
+ return vitestPackage
349
+ })
@@ -64,11 +64,17 @@ class BaseAwsSdkPlugin extends ClientPlugin {
64
64
  span.setTag('region', region)
65
65
  })
66
66
 
67
- this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response }) => {
67
+ this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response, cbExists = false }) => {
68
68
  const store = storage.getStore()
69
69
  if (!store) return
70
70
  const { span } = store
71
71
  if (!span) return
72
+ // try to extract DSM context from response if no callback exists as extraction normally happens in CB
73
+ if (!cbExists && this.serviceIdentifier === 'sqs') {
74
+ const params = response.request.params
75
+ const operation = response.request.operation
76
+ this.responseExtractDSMContext(operation, params, response.data ?? response, span)
77
+ }
72
78
  this.addResponseTags(span, response)
73
79
  this.finish(span, response, response.error)
74
80
  })
@@ -159,6 +165,7 @@ function normalizeConfig (config, serviceIdentifier) {
159
165
 
160
166
  return Object.assign({}, config, specificConfig, {
161
167
  splitByAwsService: config.splitByAwsService !== false,
168
+ batchPropagationEnabled: config.batchPropagationEnabled !== false,
162
169
  hooks
163
170
  })
164
171
  }
@@ -21,7 +21,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
21
21
  // batch operations have different format, collect table name for batch
22
22
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#batchGetItem-property`
23
23
  // dynamoDB batch TableName
24
- if (params.RequestItems) {
24
+ if (params.RequestItems !== null) {
25
25
  if (typeof params.RequestItems === 'object') {
26
26
  if (Object.keys(params.RequestItems).length === 1) {
27
27
  const tableName = Object.keys(params.RequestItems)[0]
@@ -52,7 +52,7 @@ class Kinesis extends BaseAwsSdkPlugin {
52
52
 
53
53
  // extract DSM context after as we might not have a parent-child but may have a DSM context
54
54
  this.responseExtractDSMContext(
55
- request.operation, response, span || null, streamName
55
+ request.operation, request.params, response, span || null, { streamName }
56
56
  )
57
57
  }
58
58
  })
@@ -100,7 +100,8 @@ class Kinesis extends BaseAwsSdkPlugin {
100
100
  }
101
101
  }
102
102
 
103
- responseExtractDSMContext (operation, response, span, streamName) {
103
+ responseExtractDSMContext (operation, params, response, span, kwargs = {}) {
104
+ const { streamName } = kwargs
104
105
  if (!this.config.dsmEnabled) return
105
106
  if (operation !== 'getRecords') return
106
107
  if (!response || !response.Records || !response.Records[0]) return
@@ -151,7 +152,12 @@ class Kinesis extends BaseAwsSdkPlugin {
151
152
  case 'putRecords':
152
153
  stream = params.StreamArn ? params.StreamArn : (params.StreamName ? params.StreamName : '')
153
154
  for (let i = 0; i < params.Records.length; i++) {
154
- this.injectToMessage(span, params.Records[i], stream, i === 0)
155
+ this.injectToMessage(
156
+ span,
157
+ params.Records[i],
158
+ stream,
159
+ i === 0 || (this.config.kinesis && this.config.kinesis.batchPropagationEnabled)
160
+ )
155
161
  }
156
162
  }
157
163
  }
@@ -59,7 +59,12 @@ class Sns extends BaseAwsSdkPlugin {
59
59
  break
60
60
  case 'publishBatch':
61
61
  for (let i = 0; i < params.PublishBatchRequestEntries.length; i++) {
62
- this.injectToMessage(span, params.PublishBatchRequestEntries[i], params.TopicArn, i === 0)
62
+ this.injectToMessage(
63
+ span,
64
+ params.PublishBatchRequestEntries[i],
65
+ params.TopicArn,
66
+ i === 0 || (this.config.sns && this.config.sns.batchPropagationEnabled)
67
+ )
63
68
  }
64
69
  break
65
70
  }
@@ -23,7 +23,7 @@ class Sqs extends BaseAwsSdkPlugin {
23
23
  const plugin = this
24
24
  const contextExtraction = this.responseExtract(request.params, request.operation, response)
25
25
  let span
26
- let parsedMessageAttributes
26
+ let parsedMessageAttributes = null
27
27
  if (contextExtraction && contextExtraction.datadogContext) {
28
28
  obj.needsFinish = true
29
29
  const options = {
@@ -39,8 +39,9 @@ class Sqs extends BaseAwsSdkPlugin {
39
39
  this.enter(span, store)
40
40
  }
41
41
  // extract DSM context after as we might not have a parent-child but may have a DSM context
42
+
42
43
  this.responseExtractDSMContext(
43
- request.operation, request.params, response, span || null, parsedMessageAttributes || null
44
+ request.operation, request.params, response, span || null, { parsedMessageAttributes }
44
45
  )
45
46
  })
46
47
 
@@ -165,7 +166,8 @@ class Sqs extends BaseAwsSdkPlugin {
165
166
  }
166
167
  }
167
168
 
168
- responseExtractDSMContext (operation, params, response, span, parsedAttributes) {
169
+ responseExtractDSMContext (operation, params, response, span, kwargs = {}) {
170
+ let { parsedAttributes } = kwargs
169
171
  if (!this.config.dsmEnabled) return
170
172
  if (operation !== 'receiveMessage') return
171
173
  if (!response || !response.Messages || !response.Messages[0]) return
@@ -188,7 +190,7 @@ class Sqs extends BaseAwsSdkPlugin {
188
190
  // SQS to SQS
189
191
  }
190
192
  }
191
- if (message.MessageAttributes && message.MessageAttributes._datadog) {
193
+ if (!parsedAttributes && message.MessageAttributes && message.MessageAttributes._datadog) {
192
194
  parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
193
195
  }
194
196
  }
@@ -216,7 +218,23 @@ class Sqs extends BaseAwsSdkPlugin {
216
218
  break
217
219
  case 'sendMessageBatch':
218
220
  for (let i = 0; i < params.Entries.length; i++) {
219
- this.injectToMessage(span, params.Entries[i], params.QueueUrl, i === 0)
221
+ this.injectToMessage(
222
+ span,
223
+ params.Entries[i],
224
+ params.QueueUrl,
225
+ i === 0 || (this.config.sqs && this.config.sqs.batchPropagationEnabled)
226
+ )
227
+ }
228
+ break
229
+ case 'receiveMessage':
230
+ if (!params.MessageAttributeNames) {
231
+ params.MessageAttributeNames = ['_datadog']
232
+ } else if (
233
+ !params.MessageAttributeNames.includes('_datadog') &&
234
+ !params.MessageAttributeNames.includes('.*') &&
235
+ !params.MessageAttributeNames.includes('All')
236
+ ) {
237
+ params.MessageAttributeNames.push('_datadog')
220
238
  }
221
239
  break
222
240
  }
@@ -47,7 +47,7 @@ class Stepfunctions extends BaseAwsSdkPlugin {
47
47
 
48
48
  try {
49
49
  const inputObj = JSON.parse(input)
50
- if (inputObj && typeof inputObj === 'object') {
50
+ if (inputObj !== null && typeof inputObj === 'object') {
51
51
  // We've parsed the input JSON string
52
52
  inputObj._datadog = {}
53
53
  this.tracer.inject(span, 'text_map', inputObj._datadog)
@@ -54,7 +54,7 @@ class ChildProcessPlugin extends TracingPlugin {
54
54
  }
55
55
 
56
56
  this.startSpan('command_execution', {
57
- service: this.config.service,
57
+ service: this.config.service || this._tracerConfig.service,
58
58
  resource: (shell === true) ? 'sh' : cmdFields[0],
59
59
  type: 'system',
60
60
  meta