dd-trace 5.22.0 → 5.23.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 (89) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +9 -3
  4. package/packages/datadog-instrumentations/src/cucumber.js +290 -53
  5. package/packages/datadog-instrumentations/src/jest.js +3 -1
  6. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  7. package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
  8. package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  11. package/packages/datadog-instrumentations/src/openai.js +4 -2
  12. package/packages/datadog-instrumentations/src/pg.js +59 -4
  13. package/packages/datadog-instrumentations/src/vitest.js +184 -9
  14. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  17. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  19. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
  21. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  22. package/packages/datadog-plugin-http/src/client.js +1 -42
  23. package/packages/datadog-plugin-http2/src/client.js +1 -26
  24. package/packages/datadog-plugin-jest/src/index.js +17 -1
  25. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  27. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  28. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  29. package/packages/datadog-plugin-openai/src/index.js +27 -18
  30. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  31. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  32. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  33. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  34. package/packages/dd-trace/src/appsec/channels.js +4 -2
  35. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  36. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  37. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  39. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  40. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  41. package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  43. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  44. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
  46. package/packages/dd-trace/src/config.js +61 -10
  47. package/packages/dd-trace/src/constants.js +11 -1
  48. package/packages/dd-trace/src/data_streams_context.js +3 -0
  49. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  50. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  51. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  52. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  53. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  54. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  60. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  62. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  63. package/packages/dd-trace/src/debugger/index.js +92 -0
  64. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  66. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  67. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  68. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  69. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  70. package/packages/dd-trace/src/plugin_manager.js +11 -10
  71. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  72. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  73. package/packages/dd-trace/src/plugins/util/test.js +26 -2
  74. package/packages/dd-trace/src/profiling/config.js +5 -0
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  76. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  77. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  78. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  79. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  80. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  81. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  82. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  83. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  84. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  85. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  86. package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
  87. package/packages/dd-trace/src/proxy.js +10 -3
  88. package/packages/dd-trace/src/span_stats.js +4 -2
  89. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -53,14 +53,15 @@ function wrapQuery (query) {
53
53
  }
54
54
 
55
55
  return asyncResource.runInAsyncScope(() => {
56
+ const abortController = new AbortController()
57
+
56
58
  startCh.publish({
57
59
  params: this.connectionParameters,
58
60
  query: pgQuery,
59
- processId
61
+ processId,
62
+ abortController
60
63
  })
61
64
 
62
- arguments[0] = pgQuery
63
-
64
65
  const finish = asyncResource.bind(function (error) {
65
66
  if (error) {
66
67
  errorCh.publish(error)
@@ -68,6 +69,43 @@ function wrapQuery (query) {
68
69
  finishCh.publish()
69
70
  })
70
71
 
72
+ if (abortController.signal.aborted) {
73
+ const error = abortController.signal.reason || new Error('Aborted')
74
+
75
+ // eslint-disable-next-line max-len
76
+ // Based on: https://github.com/brianc/node-postgres/blob/54eb0fa216aaccd727765641e7d1cf5da2bc483d/packages/pg/lib/client.js#L510
77
+ const reusingQuery = typeof pgQuery.submit === 'function'
78
+ const callback = arguments[arguments.length - 1]
79
+
80
+ finish(error)
81
+
82
+ if (reusingQuery) {
83
+ if (!pgQuery.callback && typeof callback === 'function') {
84
+ pgQuery.callback = callback
85
+ }
86
+
87
+ if (pgQuery.callback) {
88
+ pgQuery.callback(error)
89
+ } else {
90
+ process.nextTick(() => {
91
+ pgQuery.emit('error', error)
92
+ })
93
+ }
94
+
95
+ return pgQuery
96
+ }
97
+
98
+ if (typeof callback === 'function') {
99
+ callback(error)
100
+
101
+ return
102
+ }
103
+
104
+ return Promise.reject(error)
105
+ }
106
+
107
+ arguments[0] = pgQuery
108
+
71
109
  const retval = query.apply(this, arguments)
72
110
  const queryQueue = this.queryQueue || this._queryQueue
73
111
  const activeQuery = this.activeQuery || this._activeQuery
@@ -112,8 +150,11 @@ function wrapPoolQuery (query) {
112
150
  const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
113
151
 
114
152
  return asyncResource.runInAsyncScope(() => {
153
+ const abortController = new AbortController()
154
+
115
155
  startPoolQueryCh.publish({
116
- query: pgQuery
156
+ query: pgQuery,
157
+ abortController
117
158
  })
118
159
 
119
160
  const finish = asyncResource.bind(function () {
@@ -121,6 +162,20 @@ function wrapPoolQuery (query) {
121
162
  })
122
163
 
123
164
  const cb = arguments[arguments.length - 1]
165
+
166
+ if (abortController.signal.aborted) {
167
+ const error = abortController.signal.reason || new Error('Aborted')
168
+ finish()
169
+
170
+ if (typeof cb === 'function') {
171
+ cb(error)
172
+
173
+ return
174
+ } else {
175
+ return Promise.reject(error)
176
+ }
177
+ }
178
+
124
179
  if (typeof cb === 'function') {
125
180
  arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function () {
126
181
  finish()
@@ -1,5 +1,6 @@
1
1
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
2
  const shimmer = require('../../datadog-shimmer')
3
+ const log = require('../../dd-trace/src/log')
3
4
 
4
5
  // test hooks
5
6
  const testStartCh = channel('ci:vitest:test:start')
@@ -7,6 +8,7 @@ const testFinishTimeCh = channel('ci:vitest:test:finish-time')
7
8
  const testPassCh = channel('ci:vitest:test:pass')
8
9
  const testErrorCh = channel('ci:vitest:test:error')
9
10
  const testSkipCh = channel('ci:vitest:test:skip')
11
+ const isNewTestCh = channel('ci:vitest:test:is-new')
10
12
 
11
13
  // test suite hooks
12
14
  const testSuiteStartCh = channel('ci:vitest:test-suite:start')
@@ -17,9 +19,13 @@ const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
17
19
  const testSessionStartCh = channel('ci:vitest:session:start')
18
20
  const testSessionFinishCh = channel('ci:vitest:session:finish')
19
21
  const libraryConfigurationCh = channel('ci:vitest:library-configuration')
22
+ const knownTestsCh = channel('ci:vitest:known-tests')
23
+ const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
20
24
 
21
25
  const taskToAsync = new WeakMap()
22
-
26
+ const taskToStatuses = new WeakMap()
27
+ const newTasks = new WeakSet()
28
+ const switchedStatuses = new WeakSet()
23
29
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
24
30
 
25
31
  function isReporterPackage (vitestPackage) {
@@ -108,20 +114,61 @@ function getSortWrapper (sort) {
108
114
  // will not work. This will be a known limitation.
109
115
  let isFlakyTestRetriesEnabled = false
110
116
  let flakyTestRetriesCount = 0
117
+ let isEarlyFlakeDetectionEnabled = false
118
+ let earlyFlakeDetectionNumRetries = 0
119
+ let isEarlyFlakeDetectionFaulty = false
120
+ let knownTests = {}
111
121
 
112
122
  try {
113
123
  const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
114
124
  if (!err) {
115
125
  isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
116
126
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
127
+ isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
128
+ earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
117
129
  }
118
130
  } catch (e) {
119
131
  isFlakyTestRetriesEnabled = false
132
+ isEarlyFlakeDetectionEnabled = false
120
133
  }
134
+
121
135
  if (isFlakyTestRetriesEnabled && !this.ctx.config.retry && flakyTestRetriesCount > 0) {
122
136
  this.ctx.config.retry = flakyTestRetriesCount
123
137
  }
124
138
 
139
+ if (isEarlyFlakeDetectionEnabled) {
140
+ const knownTestsResponse = await getChannelPromise(knownTestsCh)
141
+ if (!knownTestsResponse.err) {
142
+ knownTests = knownTestsResponse.knownTests
143
+ const testFilepaths = await this.ctx.getTestFilepaths()
144
+
145
+ isEarlyFlakeDetectionFaultyCh.publish({
146
+ knownTests: knownTests.vitest || {},
147
+ testFilepaths,
148
+ onDone: (isFaulty) => {
149
+ isEarlyFlakeDetectionFaulty = isFaulty
150
+ }
151
+ })
152
+ if (isEarlyFlakeDetectionFaulty) {
153
+ isEarlyFlakeDetectionEnabled = false
154
+ log.warn('Early flake detection is disabled because the number of new tests is too high.')
155
+ } else {
156
+ // TODO: use this to pass session and module IDs to the worker, instead of polluting process.env
157
+ // Note: setting this.ctx.config.provide directly does not work because it's cached
158
+ try {
159
+ const workspaceProject = this.ctx.getCoreWorkspaceProject()
160
+ workspaceProject._provided._ddKnownTests = knownTests.vitest
161
+ workspaceProject._provided._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
162
+ workspaceProject._provided._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
163
+ } catch (e) {
164
+ log.warn('Could not send known tests to workers so Early Flake Detection will not work.')
165
+ }
166
+ }
167
+ } else {
168
+ isEarlyFlakeDetectionEnabled = false
169
+ }
170
+ }
171
+
125
172
  let testCodeCoverageLinesTotal
126
173
 
127
174
  if (this.ctx.coverageProvider?.generateCoverage) {
@@ -154,6 +201,8 @@ function getSortWrapper (sort) {
154
201
  status: getSessionStatus(this.state),
155
202
  testCodeCoverageLinesTotal,
156
203
  error,
204
+ isEarlyFlakeDetectionEnabled,
205
+ isEarlyFlakeDetectionFaulty,
157
206
  onFinish
158
207
  })
159
208
  })
@@ -188,12 +237,83 @@ addHook({
188
237
  file: 'dist/runners.js'
189
238
  }, (vitestPackage) => {
190
239
  const { VitestTestRunner } = vitestPackage
240
+
241
+ // `onBeforeRunTask` is run before any repetition or attempt is run
242
+ shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => async function (task) {
243
+ const testName = getTestName(task)
244
+ try {
245
+ const {
246
+ _ddKnownTests: knownTests,
247
+ _ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled,
248
+ _ddEarlyFlakeDetectionNumRetries: numRepeats
249
+ } = globalThis.__vitest_worker__.providedContext
250
+
251
+ if (isEarlyFlakeDetectionEnabled) {
252
+ isNewTestCh.publish({
253
+ knownTests,
254
+ testSuiteAbsolutePath: task.file.filepath,
255
+ testName,
256
+ onDone: (isNew) => {
257
+ if (isNew) {
258
+ task.repeats = numRepeats
259
+ newTasks.add(task)
260
+ taskToStatuses.set(task, [])
261
+ }
262
+ }
263
+ })
264
+ }
265
+ } catch (e) {
266
+ log.error('Vitest workers could not parse known tests, so Early Flake Detection will not work.')
267
+ }
268
+
269
+ return onBeforeRunTask.apply(this, arguments)
270
+ })
271
+
272
+ // `onAfterRunTask` is run after all repetitions or attempts are run
273
+ shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => async function (task) {
274
+ const {
275
+ _ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled
276
+ } = globalThis.__vitest_worker__.providedContext
277
+
278
+ if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task)) {
279
+ const statuses = taskToStatuses.get(task)
280
+ // If the test has passed at least once, we consider it passed
281
+ if (statuses.includes('pass')) {
282
+ if (task.result.state === 'fail') {
283
+ switchedStatuses.add(task)
284
+ }
285
+ task.result.state = 'pass'
286
+ }
287
+ }
288
+
289
+ return onAfterRunTask.apply(this, arguments)
290
+ })
291
+
191
292
  // test start (only tests that are not marked as skip or todo)
293
+ // `onBeforeTryTask` is run for every repetition and attempt of the test
192
294
  shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
193
295
  if (!testStartCh.hasSubscribers) {
194
296
  return onBeforeTryTask.apply(this, arguments)
195
297
  }
196
- const { retry: numAttempt } = retryInfo
298
+ const testName = getTestName(task)
299
+ let isNew = false
300
+ let isEarlyFlakeDetectionEnabled = false
301
+
302
+ try {
303
+ const {
304
+ _ddIsEarlyFlakeDetectionEnabled
305
+ } = globalThis.__vitest_worker__.providedContext
306
+
307
+ isEarlyFlakeDetectionEnabled = _ddIsEarlyFlakeDetectionEnabled
308
+
309
+ if (isEarlyFlakeDetectionEnabled) {
310
+ isNew = newTasks.has(task)
311
+ }
312
+ } catch (e) {
313
+ log.error('Vitest workers could not parse known tests, so Early Flake Detection will not work.')
314
+ }
315
+ const { retry: numAttempt, repeats: numRepetition } = retryInfo
316
+
197
317
  // We finish the previous test here because we know it has failed already
198
318
  if (numAttempt > 0) {
199
319
  const asyncResource = taskToAsync.get(task)
@@ -205,14 +325,58 @@ addHook({
205
325
  }
206
326
  }
207
327
 
328
+ const lastExecutionStatus = task.result.state
329
+
330
+ // These clauses handle task.repeats, whether EFD is enabled or not
331
+ // The only thing that EFD does is to forcefully pass the test if it has passed at least once
332
+ if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
333
+ // Here we finish the earlier iteration,
334
+ // as long as it's not the _last_ iteration (which will be finished normally)
335
+
336
+ // TODO: check test duration (not to repeat if it's too slow)
337
+ const asyncResource = taskToAsync.get(task)
338
+ if (asyncResource) {
339
+ if (lastExecutionStatus === 'fail') {
340
+ const testError = task.result?.errors?.[0]
341
+ asyncResource.runInAsyncScope(() => {
342
+ testErrorCh.publish({ error: testError })
343
+ })
344
+ } else {
345
+ asyncResource.runInAsyncScope(() => {
346
+ testPassCh.publish({ task })
347
+ })
348
+ }
349
+ if (isEarlyFlakeDetectionEnabled) {
350
+ const statuses = taskToStatuses.get(task)
351
+ statuses.push(lastExecutionStatus)
352
+ // If we don't "reset" the result.state to "pass", once a repetition fails,
353
+ // vitest will always consider the test as failed, so we can't read the actual status
354
+ task.result.state = 'pass'
355
+ }
356
+ }
357
+ } else if (numRepetition === task.repeats) {
358
+ const asyncResource = taskToAsync.get(task)
359
+ if (lastExecutionStatus === 'fail') {
360
+ const testError = task.result?.errors?.[0]
361
+ asyncResource.runInAsyncScope(() => {
362
+ testErrorCh.publish({ error: testError })
363
+ })
364
+ } else {
365
+ asyncResource.runInAsyncScope(() => {
366
+ testPassCh.publish({ task })
367
+ })
368
+ }
369
+ }
370
+
208
371
  const asyncResource = new AsyncResource('bound-anonymous-fn')
209
372
  taskToAsync.set(task, asyncResource)
210
373
 
211
374
  asyncResource.runInAsyncScope(() => {
212
375
  testStartCh.publish({
213
- testName: getTestName(task),
376
+ testName,
214
377
  testSuiteAbsolutePath: task.file.filepath,
215
- isRetry: numAttempt > 0
378
+ isRetry: numAttempt > 0 || numRepetition > 0,
379
+ isNew
216
380
  })
217
381
  })
218
382
  return onBeforeTryTask.apply(this, arguments)
@@ -230,7 +394,7 @@ addHook({
230
394
  const asyncResource = taskToAsync.get(task)
231
395
 
232
396
  if (asyncResource) {
233
- // We don't finish here because the test might fail in a later hook
397
+ // We don't finish here because the test might fail in a later hook (afterEach)
234
398
  asyncResource.runInAsyncScope(() => {
235
399
  testFinishTimeCh.publish({ status, task })
236
400
  })
@@ -270,7 +434,16 @@ addHook({
270
434
 
271
435
  addHook({
272
436
  name: 'vitest',
273
- versions: ['>=2.0.5'],
437
+ versions: ['>=2.1.0'],
438
+ filePattern: 'dist/chunks/RandomSequencer.*'
439
+ }, (randomSequencerPackage) => {
440
+ shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
441
+ return randomSequencerPackage
442
+ })
443
+
444
+ addHook({
445
+ name: 'vitest',
446
+ versions: ['>=2.0.5 <2.1.0'],
274
447
  filePattern: 'dist/chunks/index.*'
275
448
  }, (vitestPackage) => {
276
449
  if (isReporterPackageNewest(vitestPackage)) {
@@ -323,19 +496,21 @@ addHook({
323
496
  testTasks.forEach(task => {
324
497
  const testAsyncResource = taskToAsync.get(task)
325
498
  const { result } = task
499
+ // We have to trick vitest into thinking that the test has passed
500
+ // but we want to report it as failed if it did fail
501
+ const isSwitchedStatus = switchedStatuses.has(task)
326
502
 
327
503
  if (result) {
328
504
  const { state, duration, errors } = result
329
505
  if (state === 'skip') { // programmatic skip
330
506
  testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
331
- } else if (state === 'pass') {
507
+ } else if (state === 'pass' && !isSwitchedStatus) {
332
508
  if (testAsyncResource) {
333
509
  testAsyncResource.runInAsyncScope(() => {
334
510
  testPassCh.publish({ task })
335
511
  })
336
512
  }
337
- } else if (state === 'fail') {
338
- // If it's failing, we have no accurate finish time, so we have to use `duration`
513
+ } else if (state === 'fail' || isSwitchedStatus) {
339
514
  let testError
340
515
 
341
516
  if (errors?.length) {
@@ -3,7 +3,6 @@
3
3
  const { TEXT_MAP } = require('../../../ext/formats')
4
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
5
  const { getAmqpMessageSize } = require('../../dd-trace/src/datastreams/processor')
6
- const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
7
6
  const { getResourceName } = require('./util')
8
7
 
9
8
  class AmqplibConsumerPlugin extends ConsumerPlugin {
@@ -30,8 +29,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
30
29
  })
31
30
 
32
31
  if (
33
- this.config.dsmEnabled && message?.properties?.headers &&
34
- DsmPathwayCodec.contextExists(message.properties.headers)
32
+ this.config.dsmEnabled && message?.properties?.headers
35
33
  ) {
36
34
  const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
37
35
  const queue = fields.queue ? fields.queue : fields.routingKey
@@ -5,9 +5,11 @@ const ClientPlugin = require('../../dd-trace/src/plugins/client')
5
5
  const { storage } = require('../../datadog-core')
6
6
  const { isTrue } = require('../../dd-trace/src/util')
7
7
  const coalesce = require('koalas')
8
+ const { tagsFromRequest, tagsFromResponse } = require('../../dd-trace/src/payload-tagging')
8
9
 
9
10
  class BaseAwsSdkPlugin extends ClientPlugin {
10
11
  static get id () { return 'aws' }
12
+ static get isPayloadReporter () { return false }
11
13
 
12
14
  get serviceIdentifier () {
13
15
  const id = this.constructor.id.toLowerCase()
@@ -20,6 +22,14 @@ class BaseAwsSdkPlugin extends ClientPlugin {
20
22
  return id
21
23
  }
22
24
 
25
+ get cloudTaggingConfig () {
26
+ return this._tracerConfig.cloudPayloadTagging
27
+ }
28
+
29
+ get payloadTaggingRules () {
30
+ return this.cloudTaggingConfig.rules.aws?.[this.constructor.id]
31
+ }
32
+
23
33
  constructor (...args) {
24
34
  super(...args)
25
35
 
@@ -51,6 +61,12 @@ class BaseAwsSdkPlugin extends ClientPlugin {
51
61
 
52
62
  this.requestInject(span, request)
53
63
 
64
+ if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.requestsEnabled) {
65
+ const maxDepth = this.cloudTaggingConfig.maxDepth
66
+ const requestTags = tagsFromRequest(this.payloadTaggingRules, request.params, { maxDepth })
67
+ span.addTags(requestTags)
68
+ }
69
+
54
70
  const store = storage.getStore()
55
71
 
56
72
  this.enter(span, store)
@@ -116,6 +132,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
116
132
  const params = response.request.params
117
133
  const operation = response.request.operation
118
134
  const extraTags = this.generateTags(params, operation, response) || {}
135
+
119
136
  const tags = Object.assign({
120
137
  'aws.response.request_id': response.requestId,
121
138
  'resource.name': operation,
@@ -123,6 +140,22 @@ class BaseAwsSdkPlugin extends ClientPlugin {
123
140
  }, extraTags)
124
141
 
125
142
  span.addTags(tags)
143
+
144
+ if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.responsesEnabled) {
145
+ const maxDepth = this.cloudTaggingConfig.maxDepth
146
+ const responseBody = this.extractResponseBody(response)
147
+ const responseTags = tagsFromResponse(this.payloadTaggingRules, responseBody, { maxDepth })
148
+ span.addTags(responseTags)
149
+ }
150
+ }
151
+
152
+ extractResponseBody (response) {
153
+ if (response.hasOwnProperty('data')) {
154
+ return response.data
155
+ }
156
+ return Object.fromEntries(
157
+ Object.entries(response).filter(([key]) => !['request', 'requestId', 'error', '$metadata'].includes(key))
158
+ )
126
159
  }
127
160
 
128
161
  generateTags () {
@@ -113,7 +113,7 @@ class Kinesis extends BaseAwsSdkPlugin {
113
113
  const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
114
114
 
115
115
  if (
116
- parsedAttributes?._datadog && streamName && DsmPathwayCodec.contextExists(parsedAttributes._datadog)
116
+ parsedAttributes?._datadog && streamName
117
117
  ) {
118
118
  const payloadSize = getSizeOrZero(record.Data)
119
119
  this.tracer.decodeDataStreamsContext(parsedAttributes._datadog)
@@ -7,6 +7,7 @@ const BaseAwsSdkPlugin = require('../base')
7
7
  class Sns extends BaseAwsSdkPlugin {
8
8
  static get id () { return 'sns' }
9
9
  static get peerServicePrecursors () { return ['topicname'] }
10
+ static get isPayloadReporter () { return true }
10
11
 
11
12
  generateTags (params, operation, response) {
12
13
  if (!params) return {}
@@ -20,6 +21,7 @@ class Sns extends BaseAwsSdkPlugin {
20
21
 
21
22
  // Get the topic name from the last part of the ARN
22
23
  const topicName = arnParts[arnParts.length - 1]
24
+
23
25
  return {
24
26
  'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`,
25
27
  'aws.sns.topic_arn': TopicArn,
@@ -194,7 +194,7 @@ class Sqs extends BaseAwsSdkPlugin {
194
194
  parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
195
195
  }
196
196
  }
197
- if (parsedAttributes && DsmPathwayCodec.contextExists(parsedAttributes)) {
197
+ if (parsedAttributes) {
198
198
  const payloadSize = getHeadersSize({
199
199
  Body: message.Body,
200
200
  MessageAttributes: message.MessageAttributes
@@ -17,6 +17,7 @@ const {
17
17
  ITR_CORRELATION_ID,
18
18
  TEST_SOURCE_FILE,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
+ TEST_EARLY_FLAKE_ABORT_REASON,
20
21
  TEST_IS_NEW,
21
22
  TEST_IS_RETRY,
22
23
  TEST_SUITE_ID,
@@ -79,6 +80,7 @@ class CucumberPlugin extends CiPlugin {
79
80
  hasUnskippableSuites,
80
81
  hasForcedToRunSuites,
81
82
  isEarlyFlakeDetectionEnabled,
83
+ isEarlyFlakeDetectionFaulty,
82
84
  isParallel
83
85
  }) => {
84
86
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
@@ -99,6 +101,9 @@ class CucumberPlugin extends CiPlugin {
99
101
  if (isEarlyFlakeDetectionEnabled) {
100
102
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
101
103
  }
104
+ if (isEarlyFlakeDetectionFaulty) {
105
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
106
+ }
102
107
  if (isParallel) {
103
108
  this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
104
109
  }
@@ -116,7 +121,15 @@ class CucumberPlugin extends CiPlugin {
116
121
  this.tracer._exporter.flush()
117
122
  })
118
123
 
119
- this.addSub('ci:cucumber:test-suite:start', ({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId }) => {
124
+ this.addSub('ci:cucumber:test-suite:start', ({
125
+ testFileAbsolutePath,
126
+ isUnskippable,
127
+ isForcedToRun,
128
+ itrCorrelationId
129
+ }) => {
130
+ const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
131
+ const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
132
+
120
133
  const testSuiteMetadata = getTestSuiteCommonTags(
121
134
  this.command,
122
135
  this.frameworkVersion,
@@ -134,6 +147,16 @@ class CucumberPlugin extends CiPlugin {
134
147
  if (itrCorrelationId) {
135
148
  testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
136
149
  }
150
+ if (testSourceFile) {
151
+ testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
152
+ testSuiteMetadata[TEST_SOURCE_START] = 1
153
+ }
154
+
155
+ const codeOwners = this.getCodeOwners(testSuiteMetadata)
156
+ if (codeOwners) {
157
+ testSuiteMetadata[TEST_CODE_OWNERS] = codeOwners
158
+ }
159
+
137
160
  const testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
138
161
  childOf: this.testModuleSpan,
139
162
  tags: {
@@ -28,7 +28,10 @@ const {
28
28
  TEST_SOURCE_FILE,
29
29
  TEST_IS_NEW,
30
30
  TEST_IS_RETRY,
31
- TEST_EARLY_FLAKE_ENABLED
31
+ TEST_EARLY_FLAKE_ENABLED,
32
+ getTestSessionName,
33
+ TEST_SESSION_NAME,
34
+ TEST_LEVEL_EVENT_TYPES
32
35
  } = require('../../dd-trace/src/plugins/util/test')
33
36
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
34
37
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -245,10 +248,22 @@ class CypressPlugin {
245
248
  return this.libraryConfigurationPromise
246
249
  }
247
250
 
248
- getTestSuiteSpan (suite) {
251
+ getTestSuiteSpan ({ testSuite, testSuiteAbsolutePath }) {
249
252
  const testSuiteSpanMetadata =
250
- getTestSuiteCommonTags(this.command, this.frameworkVersion, suite, TEST_FRAMEWORK_NAME)
253
+ getTestSuiteCommonTags(this.command, this.frameworkVersion, testSuite, TEST_FRAMEWORK_NAME)
254
+
251
255
  this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
256
+
257
+ if (testSuiteAbsolutePath) {
258
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
259
+ testSuiteSpanMetadata[TEST_SOURCE_FILE] = testSourceFile
260
+ testSuiteSpanMetadata[TEST_SOURCE_START] = 1
261
+ const codeOwners = this.getTestCodeOwners({ testSuite, testSourceFile })
262
+ if (codeOwners) {
263
+ testSuiteSpanMetadata[TEST_CODE_OWNERS] = codeOwners
264
+ }
265
+ }
266
+
252
267
  return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
253
268
  childOf: this.testModuleSpan,
254
269
  tags: {
@@ -387,6 +402,18 @@ class CypressPlugin {
387
402
  testSessionSpanMetadata[TEST_EARLY_FLAKE_ENABLED] = 'true'
388
403
  }
389
404
 
405
+ const testSessionName = getTestSessionName(this.tracer._tracer._config, this.command, this.testEnvironmentMetadata)
406
+
407
+ if (this.tracer._tracer._exporter?.setMetadataTags) {
408
+ const metadataTags = {}
409
+ for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
410
+ metadataTags[testLevel] = {
411
+ [TEST_SESSION_NAME]: testSessionName
412
+ }
413
+ }
414
+ this.tracer._tracer._exporter.setMetadataTags(metadataTags)
415
+ }
416
+
390
417
  this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
391
418
  childOf,
392
419
  tags: {
@@ -473,7 +500,10 @@ class CypressPlugin {
473
500
  // dd:testSuiteStart hasn't been triggered for whatever reason
474
501
  // We will create the test suite span on the spot if that's the case
475
502
  log.warn('There was an error creating the test suite event.')
476
- this.testSuiteSpan = this.getTestSuiteSpan(spec.relative)
503
+ this.testSuiteSpan = this.getTestSuiteSpan({
504
+ testSuite: spec.relative,
505
+ testSuiteAbsolutePath: spec.absolute
506
+ })
477
507
  }
478
508
 
479
509
  // Get tests that didn't go through `dd:afterEach`
@@ -584,7 +614,7 @@ class CypressPlugin {
584
614
 
585
615
  getTasks () {
586
616
  return {
587
- 'dd:testSuiteStart': (testSuite) => {
617
+ 'dd:testSuiteStart': ({ testSuite, testSuiteAbsolutePath }) => {
588
618
  const suitePayload = {
589
619
  isEarlyFlakeDetectionEnabled: this.isEarlyFlakeDetectionEnabled,
590
620
  knownTestsForSuite: this.knownTestsByTestSuite?.[testSuite] || [],
@@ -594,7 +624,7 @@ class CypressPlugin {
594
624
  if (this.testSuiteSpan) {
595
625
  return suitePayload
596
626
  }
597
- this.testSuiteSpan = this.getTestSuiteSpan(testSuite)
627
+ this.testSuiteSpan = this.getTestSuiteSpan({ testSuite, testSuiteAbsolutePath })
598
628
  return suitePayload
599
629
  },
600
630
  'dd:beforeEach': (test) => {
@@ -61,7 +61,10 @@ beforeEach(function () {
61
61
  })
62
62
 
63
63
  before(function () {
64
- cy.task('dd:testSuiteStart', Cypress.mocha.getRootSuite().file).then((suiteConfig) => {
64
+ cy.task('dd:testSuiteStart', {
65
+ testSuite: Cypress.mocha.getRootSuite().file,
66
+ testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute
67
+ }).then((suiteConfig) => {
65
68
  if (suiteConfig) {
66
69
  isEarlyFlakeDetectionEnabled = suiteConfig.isEarlyFlakeDetectionEnabled
67
70
  knownTestsForSuite = suiteConfig.knownTestsForSuite