dd-trace 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +1 -32
  3. package/ci/init.js +1 -4
  4. package/index.d.ts +21 -0
  5. package/package.json +7 -6
  6. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  7. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +12 -12
  9. package/packages/datadog-instrumentations/src/express.js +20 -0
  10. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  12. package/packages/datadog-instrumentations/src/jest.js +149 -11
  13. package/packages/datadog-instrumentations/src/mocha.js +142 -16
  14. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  15. package/packages/datadog-instrumentations/src/next.js +17 -3
  16. package/packages/datadog-instrumentations/src/playwright.js +41 -9
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
  18. package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +107 -1
  20. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  21. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  22. package/packages/datadog-plugin-cucumber/src/index.js +16 -11
  23. package/packages/datadog-plugin-cypress/src/plugin.js +52 -23
  24. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  25. package/packages/datadog-plugin-http/src/client.js +1 -1
  26. package/packages/datadog-plugin-jest/src/index.js +43 -6
  27. package/packages/datadog-plugin-kafkajs/src/consumer.js +16 -0
  28. package/packages/datadog-plugin-mocha/src/index.js +47 -17
  29. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  30. package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
  31. package/packages/datadog-plugin-rhea/src/producer.js +11 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
  34. package/packages/dd-trace/src/appsec/channels.js +2 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  38. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  39. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +12 -1
  41. package/packages/dd-trace/src/appsec/iast/index.js +4 -4
  42. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  48. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  49. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  50. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +29 -2
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  52. package/packages/dd-trace/src/appsec/index.js +17 -2
  53. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  54. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  55. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
  57. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  59. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
  60. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
  62. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
  63. package/packages/dd-trace/src/config.js +22 -9
  64. package/packages/dd-trace/src/datastreams/processor.js +6 -0
  65. package/packages/dd-trace/src/datastreams/writer.js +2 -5
  66. package/packages/dd-trace/src/dogstatsd.js +3 -5
  67. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
  68. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  69. package/packages/dd-trace/src/format.js +25 -1
  70. package/packages/dd-trace/src/noop/span.js +1 -0
  71. package/packages/dd-trace/src/opentelemetry/span.js +9 -2
  72. package/packages/dd-trace/src/opentracing/span.js +38 -0
  73. package/packages/dd-trace/src/opentracing/span_context.js +12 -6
  74. package/packages/dd-trace/src/opentracing/tracer.js +2 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +25 -9
  76. package/packages/dd-trace/src/plugins/index.js +1 -0
  77. package/packages/dd-trace/src/plugins/util/git.js +6 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +53 -8
  79. package/packages/dd-trace/src/profiling/config.js +22 -22
  80. package/packages/dd-trace/src/proxy.js +31 -23
  81. package/packages/dd-trace/src/span_processor.js +5 -1
  82. package/packages/dd-trace/src/telemetry/index.js +6 -0
  83. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  84. package/packages/dd-trace/src/telemetry/send-data.js +0 -3
  85. package/packages/datadog-instrumentations/src/child-process.js +0 -29
  86. package/packages/dd-trace/src/plugins/util/exec.js +0 -34
@@ -24,7 +24,8 @@ const {
24
24
  TEST_SKIPPED_BY_ITR,
25
25
  TEST_ITR_UNSKIPPABLE,
26
26
  TEST_ITR_FORCED_RUN,
27
- ITR_CORRELATION_ID
27
+ ITR_CORRELATION_ID,
28
+ TEST_SOURCE_FILE
28
29
  } = require('../../dd-trace/src/plugins/util/test')
29
30
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
30
31
  const log = require('../../dd-trace/src/log')
@@ -45,7 +46,8 @@ const {
45
46
  GIT_REPOSITORY_URL,
46
47
  GIT_COMMIT_SHA,
47
48
  GIT_BRANCH,
48
- CI_PROVIDER_NAME
49
+ CI_PROVIDER_NAME,
50
+ CI_WORKSPACE_PATH
49
51
  } = require('../../dd-trace/src/plugins/util/tags')
50
52
  const {
51
53
  OS_VERSION,
@@ -113,20 +115,21 @@ function getSuiteStatus (suiteStats) {
113
115
  if (suiteStats.failures !== undefined && suiteStats.failures > 0) {
114
116
  return 'fail'
115
117
  }
116
- if (suiteStats.tests !== undefined && suiteStats.tests === suiteStats.pending) {
118
+ if (suiteStats.tests !== undefined &&
119
+ (suiteStats.tests === suiteStats.pending || suiteStats.tests === suiteStats.skipped)) {
117
120
  return 'skip'
118
121
  }
119
122
  return 'pass'
120
123
  }
121
124
 
122
- function getItrConfig (tracer, testConfiguration) {
125
+ function getLibraryConfiguration (tracer, testConfiguration) {
123
126
  return new Promise(resolve => {
124
- if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
127
+ if (!tracer._tracer._exporter?.getLibraryConfiguration) {
125
128
  return resolve({ err: new Error('CI Visibility was not initialized correctly') })
126
129
  }
127
130
 
128
- tracer._tracer._exporter.getItrConfiguration(testConfiguration, (err, itrConfig) => {
129
- resolve({ err, itrConfig })
131
+ tracer._tracer._exporter.getLibraryConfiguration(testConfiguration, (err, libraryConfig) => {
132
+ resolve({ err, libraryConfig })
130
133
  })
131
134
  })
132
135
  }
@@ -136,7 +139,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
136
139
  return Promise.resolve({ skippableTests: [] })
137
140
  }
138
141
  return new Promise(resolve => {
139
- if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
142
+ if (!tracer._tracer._exporter?.getLibraryConfiguration) {
140
143
  return resolve({ err: new Error('CI Visibility was not initialized correctly') })
141
144
  }
142
145
  tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
@@ -186,7 +189,8 @@ module.exports = (on, config) => {
186
189
  [RUNTIME_NAME]: runtimeName,
187
190
  [RUNTIME_VERSION]: runtimeVersion,
188
191
  [GIT_BRANCH]: branch,
189
- [CI_PROVIDER_NAME]: ciProviderName
192
+ [CI_PROVIDER_NAME]: ciProviderName,
193
+ [CI_WORKSPACE_PATH]: repositoryRoot
190
194
  } = testEnvironmentMetadata
191
195
 
192
196
  const isUnsupportedCIProvider = !ciProviderName
@@ -205,7 +209,7 @@ module.exports = (on, config) => {
205
209
  testLevel: 'test'
206
210
  }
207
211
 
208
- const codeOwnersEntries = getCodeOwnersFileEntries()
212
+ const codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
209
213
 
210
214
  let activeSpan = null
211
215
  let testSessionSpan = null
@@ -243,6 +247,10 @@ module.exports = (on, config) => {
243
247
  if (testSessionSpan && testModuleSpan) {
244
248
  testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
245
249
  testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
250
+ // If testSuiteSpan couldn't be created, we'll use the testModuleSpan as the parent
251
+ if (!testSuiteSpan) {
252
+ testSuiteTags[TEST_SUITE_ID] = testModuleSpan.context().toSpanId()
253
+ }
246
254
  }
247
255
 
248
256
  const {
@@ -283,13 +291,26 @@ module.exports = (on, config) => {
283
291
  })
284
292
  }
285
293
 
294
+ function getTestSuiteSpan (suite) {
295
+ const testSuiteSpanMetadata = getTestSuiteCommonTags(command, frameworkVersion, suite, TEST_FRAMEWORK_NAME)
296
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
297
+ return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
298
+ childOf: testModuleSpan,
299
+ tags: {
300
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
301
+ ...testEnvironmentMetadata,
302
+ ...testSuiteSpanMetadata
303
+ }
304
+ })
305
+ }
306
+
286
307
  on('before:run', (details) => {
287
- return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
308
+ return getLibraryConfiguration(tracer, testConfiguration).then(({ err, libraryConfig }) => {
288
309
  if (err) {
289
310
  log.error(err)
290
311
  } else {
291
- isSuitesSkippingEnabled = itrConfig.isSuitesSkippingEnabled
292
- isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled
312
+ isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
313
+ isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
293
314
  }
294
315
 
295
316
  return getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration)
@@ -346,6 +367,13 @@ module.exports = (on, config) => {
346
367
  const cypressTests = tests || []
347
368
  const finishedTests = finishedTestsByFile[spec.relative] || []
348
369
 
370
+ if (!testSuiteSpan) {
371
+ // dd:testSuiteStart hasn't been triggered for whatever reason
372
+ // We will create the test suite span on the spot if that's the case
373
+ log.warn('There was an error creating the test suite event.')
374
+ testSuiteSpan = getTestSuiteSpan(spec.relative)
375
+ }
376
+
349
377
  // Get tests that didn't go through `dd:afterEach`
350
378
  // and create a skipped test span for each of them
351
379
  cypressTests.filter(({ title }) => {
@@ -359,6 +387,11 @@ module.exports = (on, config) => {
359
387
  cypressTestName === test.name && spec.relative === test.suite
360
388
  )
361
389
  const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
390
+ if (spec.absolute && repositoryRoot) {
391
+ skippedTestSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
392
+ } else {
393
+ skippedTestSpan.setTag(TEST_SOURCE_FILE, spec.relative)
394
+ }
362
395
  skippedTestSpan.setTag(TEST_STATUS, 'skip')
363
396
  if (isSkippedByItr) {
364
397
  skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
@@ -390,6 +423,11 @@ module.exports = (on, config) => {
390
423
  if (itrCorrelationId) {
391
424
  finishedTest.testSpan.setTag(ITR_CORRELATION_ID, itrCorrelationId)
392
425
  }
426
+ if (spec.absolute && repositoryRoot) {
427
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, repositoryRoot))
428
+ } else {
429
+ finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
430
+ }
393
431
  finishedTest.testSpan.finish(finishedTest.finishTime)
394
432
  })
395
433
 
@@ -457,16 +495,7 @@ module.exports = (on, config) => {
457
495
  if (testSuiteSpan) {
458
496
  return null
459
497
  }
460
- const testSuiteSpanMetadata = getTestSuiteCommonTags(command, frameworkVersion, suite, TEST_FRAMEWORK_NAME)
461
- testSuiteSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
462
- childOf: testModuleSpan,
463
- tags: {
464
- [COMPONENT]: TEST_FRAMEWORK_NAME,
465
- ...testEnvironmentMetadata,
466
- ...testSuiteSpanMetadata
467
- }
468
- })
469
- ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
498
+ testSuiteSpan = getTestSuiteSpan(suite)
470
499
  return null
471
500
  },
472
501
  'dd:beforeEach': (test) => {
@@ -41,7 +41,6 @@ class GrpcClientPlugin extends ClientPlugin {
41
41
  'grpc.status.code': 0
42
42
  }
43
43
  }, false)
44
-
45
44
  // needed as precursor for peer.service
46
45
  if (method.service && method.package) {
47
46
  span.setTag('rpc.service', method.package + '.' + method.service)
@@ -68,7 +67,7 @@ class GrpcClientPlugin extends ClientPlugin {
68
67
  this.addError(error, span)
69
68
  }
70
69
 
71
- finish ({ span, result }) {
70
+ finish ({ span, result, peer }) {
72
71
  if (!span) return
73
72
 
74
73
  const { code, metadata } = result || {}
@@ -80,6 +79,21 @@ class GrpcClientPlugin extends ClientPlugin {
80
79
  addMetadataTags(span, metadata, metadataFilter, 'response')
81
80
  }
82
81
 
82
+ if (peer) {
83
+ // The only scheme we want to support here is ipv[46]:port, although
84
+ // more are supported by the library
85
+ // https://github.com/grpc/grpc/blob/v1.60.0/doc/naming.md
86
+ const parts = peer.split(':')
87
+ if (parts[parts.length - 1].match(/^\d+/)) {
88
+ const port = parts[parts.length - 1]
89
+ const ip = parts.slice(0, -1).join(':')
90
+ span.setTag('network.destination.ip', ip)
91
+ span.setTag('network.destination.port', port)
92
+ } else {
93
+ span.setTag('network.destination.ip', peer)
94
+ }
95
+ }
96
+
83
97
  this.tagPeerService(span)
84
98
  span.finish()
85
99
  }
@@ -122,7 +122,7 @@ class HttpClientPlugin extends ClientPlugin {
122
122
  // conditions for no error:
123
123
  // 1. not using a custom agent instance with custom timeout specified
124
124
  // 2. no invocation of `req.setTimeout`
125
- if (!args.options.agent?.options.timeout && !customRequestTimeout) return
125
+ if (!args.options.agent?.options?.timeout && !customRequestTimeout) return
126
126
 
127
127
  span.setTag('error', 1)
128
128
  }
@@ -14,7 +14,11 @@ const {
14
14
  TEST_ITR_UNSKIPPABLE,
15
15
  TEST_ITR_FORCED_RUN,
16
16
  TEST_CODE_OWNERS,
17
- ITR_CORRELATION_ID
17
+ ITR_CORRELATION_ID,
18
+ TEST_SOURCE_FILE,
19
+ TEST_IS_NEW,
20
+ TEST_EARLY_FLAKE_IS_RETRY,
21
+ TEST_EARLY_FLAKE_IS_ENABLED
18
22
  } = require('../../dd-trace/src/plugins/util/test')
19
23
  const { COMPONENT } = require('../../dd-trace/src/constants')
20
24
  const id = require('../../dd-trace/src/id')
@@ -81,7 +85,9 @@ class JestPlugin extends CiPlugin {
81
85
  numSkippedSuites,
82
86
  hasUnskippableSuites,
83
87
  hasForcedToRunSuites,
84
- error
88
+ error,
89
+ isEarlyFlakeDetectionEnabled,
90
+ onDone
85
91
  }) => {
86
92
  this.testSessionSpan.setTag(TEST_STATUS, status)
87
93
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -106,23 +112,35 @@ class JestPlugin extends CiPlugin {
106
112
  }
107
113
  )
108
114
 
115
+ if (isEarlyFlakeDetectionEnabled) {
116
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
117
+ }
118
+
109
119
  this.testModuleSpan.finish()
110
120
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
111
121
  this.testSessionSpan.finish()
112
122
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
113
123
  finishAllTraceSpans(this.testSessionSpan)
114
- this.tracer._exporter.flush()
124
+
125
+ this.tracer._exporter.flush(() => {
126
+ if (onDone) {
127
+ onDone()
128
+ }
129
+ })
115
130
  })
116
131
 
117
132
  // Test suites can be run in a different process from jest's main one.
118
133
  // This subscriber changes the configuration objects from jest to inject the trace id
119
- // of the test session to the processes that run the test suites.
134
+ // of the test session to the processes that run the test suites, and other data.
120
135
  this.addSub('ci:jest:session:configuration', configs => {
121
136
  configs.forEach(config => {
122
137
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
123
138
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
124
139
  config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
125
140
  config._ddItrCorrelationId = this.itrCorrelationId
141
+ config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
142
+ config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
143
+ config._ddRepositoryRoot = this.repositoryRoot
126
144
  })
127
145
  })
128
146
 
@@ -223,7 +241,7 @@ class JestPlugin extends CiPlugin {
223
241
  })
224
242
 
225
243
  /**
226
- * This can't use `this.itrConfig` like `ci:mocha:test-suite:code-coverage`
244
+ * This can't use `this.libraryConfig` like `ci:mocha:test-suite:code-coverage`
227
245
  * because this subscription happens in a different process from the one
228
246
  * fetching the ITR config.
229
247
  */
@@ -286,7 +304,17 @@ class JestPlugin extends CiPlugin {
286
304
  }
287
305
 
288
306
  startTestSpan (test) {
289
- const { suite, name, runner, testParameters, frameworkVersion, testStartLine } = test
307
+ const {
308
+ suite,
309
+ name,
310
+ runner,
311
+ testParameters,
312
+ frameworkVersion,
313
+ testStartLine,
314
+ testSourceFile,
315
+ isNew,
316
+ isEfdRetry
317
+ } = test
290
318
 
291
319
  const extraTags = {
292
320
  [JEST_TEST_RUNNER]: runner,
@@ -296,6 +324,15 @@ class JestPlugin extends CiPlugin {
296
324
  if (testStartLine) {
297
325
  extraTags[TEST_SOURCE_START] = testStartLine
298
326
  }
327
+ // If for whatever we don't have the source file, we'll fall back to the suite name
328
+ extraTags[TEST_SOURCE_FILE] = testSourceFile || suite
329
+
330
+ if (isNew) {
331
+ extraTags[TEST_IS_NEW] = 'true'
332
+ if (isEfdRetry) {
333
+ extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
334
+ }
335
+ }
299
336
 
300
337
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
301
338
  }
@@ -1,8 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const dc = require('dc-polyfill')
3
4
  const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
4
5
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
6
 
7
+ const afterStartCh = dc.channel('dd-trace:kafkajs:consumer:afterStart')
8
+ const beforeFinishCh = dc.channel('dd-trace:kafkajs:consumer:beforeFinish')
9
+
6
10
  class KafkajsConsumerPlugin extends ConsumerPlugin {
7
11
  static get id () { return 'kafkajs' }
8
12
  static get operation () { return 'consume' }
@@ -79,6 +83,18 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
79
83
  this.tracer
80
84
  .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], span, payloadSize)
81
85
  }
86
+
87
+ if (afterStartCh.hasSubscribers) {
88
+ afterStartCh.publish({ topic, partition, message, groupId })
89
+ }
90
+ }
91
+
92
+ finish () {
93
+ if (beforeFinishCh.hasSubscribers) {
94
+ beforeFinishCh.publish()
95
+ }
96
+
97
+ super.finish()
82
98
  }
83
99
  }
84
100
 
@@ -15,7 +15,12 @@ const {
15
15
  TEST_ITR_UNSKIPPABLE,
16
16
  TEST_ITR_FORCED_RUN,
17
17
  TEST_CODE_OWNERS,
18
- ITR_CORRELATION_ID
18
+ ITR_CORRELATION_ID,
19
+ TEST_SOURCE_FILE,
20
+ removeEfdStringFromTestName,
21
+ TEST_IS_NEW,
22
+ TEST_EARLY_FLAKE_IS_RETRY,
23
+ TEST_EARLY_FLAKE_IS_ENABLED
19
24
  } = require('../../dd-trace/src/plugins/util/test')
20
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
21
26
  const {
@@ -38,11 +43,11 @@ class MochaPlugin extends CiPlugin {
38
43
  super(...args)
39
44
 
40
45
  this._testSuites = new Map()
41
- this._testNameToParams = {}
46
+ this._testTitleToParams = {}
42
47
  this.sourceRoot = process.cwd()
43
48
 
44
49
  this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
45
- if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
50
+ if (!this.libraryConfig?.isCodeCoverageEnabled) {
46
51
  return
47
52
  }
48
53
  const testSuiteSpan = this._testSuites.get(suiteFile)
@@ -98,7 +103,7 @@ class MochaPlugin extends CiPlugin {
98
103
  }
99
104
  })
100
105
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
101
- if (this.itrConfig?.isCodeCoverageEnabled) {
106
+ if (this.libraryConfig?.isCodeCoverageEnabled) {
102
107
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
103
108
  }
104
109
  if (itrCorrelationId) {
@@ -130,9 +135,9 @@ class MochaPlugin extends CiPlugin {
130
135
  }
131
136
  })
132
137
 
133
- this.addSub('ci:mocha:test:start', ({ test, testStartLine }) => {
138
+ this.addSub('ci:mocha:test:start', (testInfo) => {
134
139
  const store = storage.getStore()
135
- const span = this.startTestSpan(test, testStartLine)
140
+ const span = this.startTestSpan(testInfo)
136
141
 
137
142
  this.enter(span, store)
138
143
  })
@@ -155,12 +160,12 @@ class MochaPlugin extends CiPlugin {
155
160
  }
156
161
  })
157
162
 
158
- this.addSub('ci:mocha:test:skip', (test) => {
163
+ this.addSub('ci:mocha:test:skip', (testInfo) => {
159
164
  const store = storage.getStore()
160
165
  // skipped through it.skip, so the span is not created yet
161
166
  // for this test
162
167
  if (!store) {
163
- const testSpan = this.startTestSpan(test)
168
+ const testSpan = this.startTestSpan(testInfo)
164
169
  this.enter(testSpan, store)
165
170
  }
166
171
  })
@@ -178,8 +183,8 @@ class MochaPlugin extends CiPlugin {
178
183
  }
179
184
  })
180
185
 
181
- this.addSub('ci:mocha:test:parameterize', ({ name, params }) => {
182
- this._testNameToParams[name] = params
186
+ this.addSub('ci:mocha:test:parameterize', ({ title, params }) => {
187
+ this._testTitleToParams[title] = params
183
188
  })
184
189
 
185
190
  this.addSub('ci:mocha:session:finish', ({
@@ -189,10 +194,11 @@ class MochaPlugin extends CiPlugin {
189
194
  numSkippedSuites,
190
195
  hasForcedToRunSuites,
191
196
  hasUnskippableSuites,
192
- error
197
+ error,
198
+ isEarlyFlakeDetectionEnabled
193
199
  }) => {
194
200
  if (this.testSessionSpan) {
195
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
201
+ const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
196
202
  this.testSessionSpan.setTag(TEST_STATUS, status)
197
203
  this.testModuleSpan.setTag(TEST_STATUS, status)
198
204
 
@@ -216,23 +222,34 @@ class MochaPlugin extends CiPlugin {
216
222
  }
217
223
  )
218
224
 
225
+ if (isEarlyFlakeDetectionEnabled) {
226
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
227
+ }
228
+
219
229
  this.testModuleSpan.finish()
220
230
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
221
231
  this.testSessionSpan.finish()
222
232
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
223
233
  finishAllTraceSpans(this.testSessionSpan)
224
234
  }
225
- this.itrConfig = null
235
+ this.libraryConfig = null
226
236
  this.tracer._exporter.flush()
227
237
  })
228
238
  }
229
239
 
230
- startTestSpan (test, testStartLine) {
231
- const testName = test.fullTitle()
232
- const { file: testSuiteAbsolutePath, title } = test
240
+ startTestSpan (testInfo) {
241
+ const {
242
+ testSuiteAbsolutePath,
243
+ title,
244
+ isNew,
245
+ isEfdRetry,
246
+ testStartLine
247
+ } = testInfo
248
+
249
+ const testName = removeEfdStringFromTestName(testInfo.testName)
233
250
 
234
251
  const extraTags = {}
235
- const testParametersString = getTestParametersString(this._testNameToParams, title)
252
+ const testParametersString = getTestParametersString(this._testTitleToParams, title)
236
253
  if (testParametersString) {
237
254
  extraTags[TEST_PARAMETERS] = testParametersString
238
255
  }
@@ -244,6 +261,19 @@ class MochaPlugin extends CiPlugin {
244
261
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
245
262
  const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
246
263
 
264
+ if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) {
265
+ extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
266
+ } else {
267
+ extraTags[TEST_SOURCE_FILE] = testSuite
268
+ }
269
+
270
+ if (isNew) {
271
+ extraTags[TEST_IS_NEW] = 'true'
272
+ if (isEfdRetry) {
273
+ extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
274
+ }
275
+ }
276
+
247
277
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
248
278
  }
249
279
  }
@@ -9,7 +9,9 @@ const {
9
9
  getTestSuitePath,
10
10
  getTestSuiteCommonTags,
11
11
  TEST_SOURCE_START,
12
- TEST_CODE_OWNERS
12
+ TEST_CODE_OWNERS,
13
+ TEST_SOURCE_FILE,
14
+ TEST_CONFIGURATION_BROWSER_NAME
13
15
  } = require('../../dd-trace/src/plugins/util/test')
14
16
  const { RESOURCE_NAME } = require('../../../ext/tags')
15
17
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -76,10 +78,11 @@ class PlaywrightPlugin extends CiPlugin {
76
78
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
77
79
  })
78
80
 
79
- this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
81
+ this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine, browserName }) => {
80
82
  const store = storage.getStore()
81
83
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
82
- const span = this.startTestSpan(testName, testSuite, testSourceLine)
84
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
85
+ const span = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine, browserName)
83
86
 
84
87
  this.enter(span, store)
85
88
  })
@@ -126,9 +129,20 @@ class PlaywrightPlugin extends CiPlugin {
126
129
  })
127
130
  }
128
131
 
129
- startTestSpan (testName, testSuite, testSourceLine) {
132
+ startTestSpan (testName, testSuite, testSourceFile, testSourceLine, browserName) {
130
133
  const testSuiteSpan = this._testSuites.get(testSuite)
131
- return super.startTestSpan(testName, testSuite, testSuiteSpan, { [TEST_SOURCE_START]: testSourceLine })
134
+
135
+ const extraTags = {
136
+ [TEST_SOURCE_START]: testSourceLine
137
+ }
138
+ if (testSourceFile) {
139
+ extraTags[TEST_SOURCE_FILE] = testSourceFile || testSuite
140
+ }
141
+ if (browserName) {
142
+ extraTags[TEST_CONFIGURATION_BROWSER_NAME] = browserName
143
+ }
144
+
145
+ return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
132
146
  }
133
147
  }
134
148
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
4
  const { storage } = require('../../datadog-core')
5
+ const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
5
6
 
6
7
  class RheaConsumerPlugin extends ConsumerPlugin {
7
8
  static get id () { return 'rhea' }
@@ -19,7 +20,7 @@ class RheaConsumerPlugin extends ConsumerPlugin {
19
20
  const name = getResourceNameFromMessage(msgObj)
20
21
  const childOf = extractTextMap(msgObj, this.tracer)
21
22
 
22
- this.startSpan({
23
+ const span = this.startSpan({
23
24
  childOf,
24
25
  resource: name,
25
26
  type: 'worker',
@@ -29,6 +30,15 @@ class RheaConsumerPlugin extends ConsumerPlugin {
29
30
  'amqp.link.role': 'receiver'
30
31
  }
31
32
  })
33
+
34
+ if (this.config.dsmEnabled && msgObj.message) {
35
+ const payloadSize = getAmqpMessageSize(
36
+ { headers: msgObj.message.delivery_annotations, content: msgObj.message.body }
37
+ )
38
+ this.tracer.decodeDataStreamsContext(msgObj.message.delivery_annotations[CONTEXT_PROPAGATION_KEY])
39
+ this.tracer
40
+ .setCheckpoint(['direction:in', `topic:${name}`, 'type:rabbitmq'], span, payloadSize)
41
+ }
32
42
  }
33
43
  }
34
44
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
4
4
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
5
+ const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
6
+ const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
5
7
 
6
8
  class RheaProducerPlugin extends ProducerPlugin {
7
9
  static get id () { return 'rhea' }
@@ -36,6 +38,15 @@ function addDeliveryAnnotations (msg, tracer, span) {
36
38
  msg.delivery_annotations = msg.delivery_annotations || {}
37
39
 
38
40
  tracer.inject(span, 'text_map', msg.delivery_annotations)
41
+
42
+ if (tracer._config.dsmEnabled) {
43
+ const targetName = span.context()._tags['amqp.link.target.address']
44
+ const payloadSize = getAmqpMessageSize({ content: msg.body, headers: msg.delivery_annotations })
45
+ const dataStreamsContext = tracer
46
+ .setCheckpoint(['direction:out', `exchange:${targetName}`, 'type:rabbitmq'], span, payloadSize)
47
+ const pathwayCtx = encodePathwayContext(dataStreamsContext)
48
+ msg.delivery_annotations[CONTEXT_PROPAGATION_KEY] = pathwayCtx
49
+ }
39
50
  }
40
51
  }
41
52
 
@@ -15,6 +15,8 @@ module.exports = {
15
15
  HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
16
16
  HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
17
17
 
18
+ HTTP_OUTGOING_BODY: 'server.response.body',
19
+
18
20
  HTTP_CLIENT_IP: 'http.client_ip',
19
21
 
20
22
  USER_ID: 'usr.id',
@@ -5,6 +5,8 @@ const log = require('../log')
5
5
  let enabled
6
6
  let requestSampling
7
7
 
8
+ const sampledRequests = new WeakSet()
9
+
8
10
  function configure ({ apiSecurity }) {
9
11
  enabled = apiSecurity.enabled
10
12
  setRequestSampling(apiSecurity.requestSampling)
@@ -32,17 +34,28 @@ function parseRequestSampling (requestSampling) {
32
34
  return parsed
33
35
  }
34
36
 
35
- function sampleRequest () {
37
+ function sampleRequest (req) {
36
38
  if (!enabled || !requestSampling) {
37
39
  return false
38
40
  }
39
41
 
40
- return Math.random() <= requestSampling
42
+ const shouldSample = Math.random() <= requestSampling
43
+
44
+ if (shouldSample) {
45
+ sampledRequests.add(req)
46
+ }
47
+
48
+ return shouldSample
49
+ }
50
+
51
+ function isSampled (req) {
52
+ return sampledRequests.has(req)
41
53
  }
42
54
 
43
55
  module.exports = {
44
56
  configure,
45
57
  disable,
46
58
  setRequestSampling,
47
- sampleRequest
59
+ sampleRequest,
60
+ isSampled
48
61
  }
@@ -16,5 +16,6 @@ module.exports = {
16
16
  queryParser: dc.channel('datadog:query:read:finish'),
17
17
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
18
18
  nextBodyParsed: dc.channel('apm:next:body-parsed'),
19
- nextQueryParsed: dc.channel('apm:next:query-parsed')
19
+ nextQueryParsed: dc.channel('apm:next:query-parsed'),
20
+ responseBody: dc.channel('datadog:express:response:json:start')
20
21
  }
@@ -8,7 +8,7 @@ class CommandInjectionAnalyzer extends InjectionAnalyzer {
8
8
  }
9
9
 
10
10
  onConfigure () {
11
- this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
11
+ this.addSub('tracing:datadog:child_process:execution:start', ({ command }) => this.analyze(command))
12
12
  }
13
13
  }
14
14