dd-trace 4.18.0 → 5.6.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 (209) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/LICENSE-3rdparty.csv +4 -5
  3. package/MIGRATING.md +15 -0
  4. package/README.md +20 -140
  5. package/ci/cypress/after-run.js +1 -0
  6. package/ci/cypress/after-spec.js +1 -0
  7. package/ci/init.js +1 -4
  8. package/ext/kinds.d.ts +1 -0
  9. package/ext/kinds.js +2 -1
  10. package/ext/tags.d.ts +2 -1
  11. package/ext/tags.js +6 -1
  12. package/index.d.ts +1523 -1460
  13. package/package.json +19 -19
  14. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  15. package/packages/datadog-core/src/utils/src/get.js +11 -0
  16. package/packages/datadog-core/src/utils/src/has.js +14 -0
  17. package/packages/datadog-core/src/utils/src/kebabcase.js +16 -0
  18. package/packages/datadog-core/src/utils/src/pick.js +11 -0
  19. package/packages/datadog-core/src/utils/src/set.js +16 -0
  20. package/packages/datadog-core/src/utils/src/uniq.js +5 -0
  21. package/packages/datadog-esbuild/index.js +1 -20
  22. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  23. package/packages/datadog-instrumentations/src/amqplib.js +2 -2
  24. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  25. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  26. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  27. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  28. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  29. package/packages/datadog-instrumentations/src/cucumber.js +163 -46
  30. package/packages/datadog-instrumentations/src/dns.js +2 -1
  31. package/packages/datadog-instrumentations/src/express.js +20 -0
  32. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  33. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  34. package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
  35. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  36. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -3
  37. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  38. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  39. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  40. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  41. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  42. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  43. package/packages/datadog-instrumentations/src/jest.js +239 -52
  44. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  45. package/packages/datadog-instrumentations/src/mocha.js +154 -18
  46. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
  47. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  48. package/packages/datadog-instrumentations/src/mquery.js +65 -0
  49. package/packages/datadog-instrumentations/src/net.js +10 -2
  50. package/packages/datadog-instrumentations/src/next.js +35 -9
  51. package/packages/datadog-instrumentations/src/playwright.js +110 -16
  52. package/packages/datadog-instrumentations/src/restify.js +14 -1
  53. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  54. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  55. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -1
  56. package/packages/datadog-plugin-amqplib/src/producer.js +13 -1
  57. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
  58. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +163 -27
  59. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +46 -8
  60. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +129 -22
  61. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  62. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +70 -13
  64. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  65. package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
  66. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
  67. package/packages/datadog-plugin-cypress/src/plugin.js +6 -454
  68. package/packages/datadog-plugin-cypress/src/support.js +50 -3
  69. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/index.js +1 -6
  71. package/packages/datadog-plugin-graphql/src/resolve.js +28 -18
  72. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  73. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  74. package/packages/datadog-plugin-http/src/client.js +19 -2
  75. package/packages/datadog-plugin-jest/src/index.js +118 -12
  76. package/packages/datadog-plugin-jest/src/util.js +38 -16
  77. package/packages/datadog-plugin-kafkajs/src/consumer.js +76 -6
  78. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -8
  79. package/packages/datadog-plugin-mocha/src/index.js +87 -17
  80. package/packages/datadog-plugin-next/src/index.js +40 -14
  81. package/packages/datadog-plugin-playwright/src/index.js +71 -8
  82. package/packages/datadog-plugin-rhea/src/consumer.js +16 -1
  83. package/packages/datadog-plugin-rhea/src/producer.js +10 -0
  84. package/packages/dd-trace/src/appsec/activation.js +29 -0
  85. package/packages/dd-trace/src/appsec/addresses.js +5 -1
  86. package/packages/dd-trace/src/appsec/api_security_sampler.js +61 -0
  87. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  88. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  89. package/packages/dd-trace/src/appsec/channels.js +7 -3
  90. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  91. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  92. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  94. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
  95. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  96. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  97. package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
  98. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  99. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  100. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  101. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +13 -2
  102. package/packages/dd-trace/src/appsec/iast/index.js +15 -5
  103. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  104. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -0
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  107. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  109. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  110. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  111. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +19 -6
  112. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +41 -3
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  116. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  117. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  119. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  120. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  121. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  122. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  123. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  124. package/packages/dd-trace/src/appsec/index.js +49 -33
  125. package/packages/dd-trace/src/appsec/recommended.json +1763 -106
  126. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -1
  127. package/packages/dd-trace/src/appsec/remote_config/index.js +42 -16
  128. package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
  129. package/packages/dd-trace/src/appsec/reporter.js +51 -34
  130. package/packages/dd-trace/src/appsec/rule_manager.js +11 -8
  131. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  132. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  133. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  134. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → early-flake-detection/get-known-tests.js} +17 -22
  135. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  136. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  137. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  139. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +95 -37
  140. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +134 -61
  141. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +37 -4
  142. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +131 -0
  143. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  144. package/packages/dd-trace/src/config.js +561 -470
  145. package/packages/dd-trace/src/data_streams_context.js +1 -1
  146. package/packages/dd-trace/src/datastreams/pathway.js +58 -1
  147. package/packages/dd-trace/src/datastreams/processor.js +196 -27
  148. package/packages/dd-trace/src/datastreams/writer.js +11 -5
  149. package/packages/dd-trace/src/dogstatsd.js +3 -5
  150. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -6
  151. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  152. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  153. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  154. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  155. package/packages/dd-trace/src/format.js +30 -2
  156. package/packages/dd-trace/src/id.js +12 -0
  157. package/packages/dd-trace/src/iitm.js +1 -1
  158. package/packages/dd-trace/src/log/channels.js +1 -1
  159. package/packages/dd-trace/src/noop/proxy.js +4 -0
  160. package/packages/dd-trace/src/noop/span.js +1 -0
  161. package/packages/dd-trace/src/opentelemetry/span.js +104 -4
  162. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  163. package/packages/dd-trace/src/opentracing/propagation/text_map.js +16 -7
  164. package/packages/dd-trace/src/opentracing/span.js +48 -4
  165. package/packages/dd-trace/src/opentracing/span_context.js +15 -6
  166. package/packages/dd-trace/src/opentracing/tracer.js +4 -3
  167. package/packages/dd-trace/src/plugin_manager.js +1 -1
  168. package/packages/dd-trace/src/plugins/ci_plugin.js +78 -19
  169. package/packages/dd-trace/src/plugins/database.js +1 -1
  170. package/packages/dd-trace/src/plugins/index.js +7 -0
  171. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  172. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  173. package/packages/dd-trace/src/plugins/util/git.js +104 -22
  174. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  175. package/packages/dd-trace/src/plugins/util/test.js +60 -10
  176. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  177. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  178. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  179. package/packages/dd-trace/src/priority_sampler.js +30 -38
  180. package/packages/dd-trace/src/profiler.js +5 -3
  181. package/packages/dd-trace/src/profiling/config.js +77 -24
  182. package/packages/dd-trace/src/profiling/exporters/agent.js +77 -31
  183. package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
  184. package/packages/dd-trace/src/profiling/profiler.js +33 -22
  185. package/packages/dd-trace/src/profiling/profilers/events.js +270 -0
  186. package/packages/dd-trace/src/profiling/profilers/shared.js +45 -0
  187. package/packages/dd-trace/src/profiling/profilers/space.js +18 -2
  188. package/packages/dd-trace/src/profiling/profilers/wall.js +146 -70
  189. package/packages/dd-trace/src/proxy.js +56 -24
  190. package/packages/dd-trace/src/ritm.js +1 -1
  191. package/packages/dd-trace/src/sampling_rule.js +130 -0
  192. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  193. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  194. package/packages/dd-trace/src/span_processor.js +9 -1
  195. package/packages/dd-trace/src/span_sampler.js +6 -64
  196. package/packages/dd-trace/src/spanleak.js +98 -0
  197. package/packages/dd-trace/src/startup-log.js +7 -1
  198. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  199. package/packages/dd-trace/src/telemetry/index.js +182 -53
  200. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  201. package/packages/dd-trace/src/telemetry/send-data.js +65 -7
  202. package/packages/dd-trace/src/tracer.js +12 -5
  203. package/register.js +4 -0
  204. package/scripts/install_plugin_modules.js +11 -3
  205. package/scripts/st.js +105 -0
  206. package/packages/datadog-instrumentations/src/child-process.js +0 -30
  207. package/packages/dd-trace/src/plugins/util/exec.js +0 -13
  208. package/packages/diagnostics_channel/index.js +0 -3
  209. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -8,13 +8,31 @@ const {
8
8
  const shimmer = require('../../datadog-shimmer')
9
9
 
10
10
  const producerStartCh = channel('apm:kafkajs:produce:start')
11
+ const producerCommitCh = channel('apm:kafkajs:produce:commit')
11
12
  const producerFinishCh = channel('apm:kafkajs:produce:finish')
12
13
  const producerErrorCh = channel('apm:kafkajs:produce:error')
13
14
 
14
15
  const consumerStartCh = channel('apm:kafkajs:consume:start')
16
+ const consumerCommitCh = channel('apm:kafkajs:consume:commit')
15
17
  const consumerFinishCh = channel('apm:kafkajs:consume:finish')
16
18
  const consumerErrorCh = channel('apm:kafkajs:consume:error')
17
19
 
20
+ function commitsFromEvent (event) {
21
+ const { payload: { groupId, topics } } = event
22
+ const commitList = []
23
+ for (const { topic, partitions } of topics) {
24
+ for (const { partition, offset } of partitions) {
25
+ commitList.push({
26
+ groupId,
27
+ partition,
28
+ offset,
29
+ topic
30
+ })
31
+ }
32
+ }
33
+ consumerCommitCh.publish(commitList)
34
+ }
35
+
18
36
  addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
19
37
  class Kafka extends BaseKafka {
20
38
  constructor (options) {
@@ -58,6 +76,12 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
58
76
  })
59
77
  )
60
78
 
79
+ result.then(res => {
80
+ if (producerCommitCh.hasSubscribers) {
81
+ producerCommitCh.publish(res)
82
+ }
83
+ })
84
+
61
85
  return result
62
86
  } catch (e) {
63
87
  producerErrorCh.publish(e)
@@ -75,6 +99,9 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
75
99
  }
76
100
 
77
101
  const consumer = createConsumer.apply(this, arguments)
102
+
103
+ consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
104
+
78
105
  const run = consumer.run
79
106
 
80
107
  const groupId = arguments[0].groupId
@@ -11,7 +11,9 @@ const {
11
11
  mergeCoverage,
12
12
  getTestSuitePath,
13
13
  fromCoverageMapToCoverage,
14
- getCallSites
14
+ getCallSites,
15
+ addEfdStringToTestName,
16
+ removeEfdStringFromTestName
15
17
  } = require('../../dd-trace/src/plugins/util/test')
16
18
 
17
19
  const testStartCh = channel('ci:mocha:test:start')
@@ -20,7 +22,8 @@ const skipCh = channel('ci:mocha:test:skip')
20
22
  const testFinishCh = channel('ci:mocha:test:finish')
21
23
  const parameterizedTestCh = channel('ci:mocha:test:parameterize')
22
24
 
23
- const itrConfigurationCh = channel('ci:mocha:itr-configuration')
25
+ const libraryConfigurationCh = channel('ci:mocha:library-configuration')
26
+ const knownTestsCh = channel('ci:mocha:known-tests')
24
27
  const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
25
28
 
26
29
  const testSessionStartCh = channel('ci:mocha:session:start')
@@ -40,6 +43,7 @@ const testToAr = new WeakMap()
40
43
  const originalFns = new WeakMap()
41
44
  const testFileToSuiteAr = new Map()
42
45
  const testToStartLine = new WeakMap()
46
+ const newTests = {}
43
47
 
44
48
  // `isWorker` is true if it's a Mocha worker
45
49
  let isWorker = false
@@ -53,6 +57,11 @@ let isSuitesSkipped = false
53
57
  let skippedSuites = []
54
58
  const unskippableSuites = []
55
59
  let isForcedToRun = false
60
+ let itrCorrelationId = ''
61
+ let isEarlyFlakeDetectionEnabled = false
62
+ let earlyFlakeDetectionNumRetries = 0
63
+ let isSuitesSkippingEnabled = false
64
+ let knownTests = []
56
65
 
57
66
  function getSuitesByTestFile (root) {
58
67
  const suitesByTestFile = {}
@@ -92,6 +101,29 @@ function isRetry (test) {
92
101
  return test._currentRetry !== undefined && test._currentRetry !== 0
93
102
  }
94
103
 
104
+ function getTestFullName (test) {
105
+ return `mocha.${getTestSuitePath(test.file, process.cwd())}.${removeEfdStringFromTestName(test.fullTitle())}`
106
+ }
107
+
108
+ function isNewTest (test) {
109
+ const testSuite = getTestSuitePath(test.file, process.cwd())
110
+ const testName = removeEfdStringFromTestName(test.fullTitle())
111
+ const testsForSuite = knownTests.mocha?.[testSuite] || []
112
+ return !testsForSuite.includes(testName)
113
+ }
114
+
115
+ function retryTest (test) {
116
+ const originalTestName = test.title
117
+ const suite = test.parent
118
+ for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
119
+ const clonedTest = test.clone()
120
+ clonedTest.title = addEfdStringToTestName(originalTestName, retryIndex + 1)
121
+ suite.addTest(clonedTest)
122
+ clonedTest._ddIsNew = true
123
+ clonedTest._ddIsEfdRetry = true
124
+ }
125
+ }
126
+
95
127
  function getTestAsyncResource (test) {
96
128
  if (!test.fn) {
97
129
  return testToAr.get(test)
@@ -122,6 +154,19 @@ function mochaHook (Runner) {
122
154
 
123
155
  patched.add(Runner)
124
156
 
157
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) {
158
+ if (isEarlyFlakeDetectionEnabled) {
159
+ // by the time we reach `this.on('test')`, it is too late. We need to add retries here
160
+ suite.tests.forEach(test => {
161
+ if (!test.isPending() && isNewTest(test)) {
162
+ test._ddIsNew = true
163
+ retryTest(test)
164
+ }
165
+ })
166
+ }
167
+ return runTests.apply(this, arguments)
168
+ })
169
+
125
170
  shimmer.wrap(Runner.prototype, 'run', run => function () {
126
171
  if (!testStartCh.hasSubscribers || isWorker) {
127
172
  return run.apply(this, arguments)
@@ -143,6 +188,24 @@ function mochaHook (Runner) {
143
188
  status = 'fail'
144
189
  }
145
190
 
191
+ if (isEarlyFlakeDetectionEnabled) {
192
+ /**
193
+ * If Early Flake Detection (EFD) is enabled the logic is as follows:
194
+ * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
195
+ * - If just a single attempt passes, we will prevent the test process from failing.
196
+ * The rationale behind is the following: you may still be able to block your CI pipeline by gating
197
+ * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
198
+ */
199
+ for (const tests of Object.values(newTests)) {
200
+ const failingNewTests = tests.filter(test => test.isFailed())
201
+ const areAllNewTestsFailing = failingNewTests.length === tests.length
202
+ if (failingNewTests.length && !areAllNewTestsFailing) {
203
+ this.stats.failures -= failingNewTests.length
204
+ this.failures -= failingNewTests.length
205
+ }
206
+ }
207
+ }
208
+
146
209
  if (status === 'fail') {
147
210
  error = new Error(`Failed tests: ${this.failures}.`)
148
211
  }
@@ -167,7 +230,8 @@ function mochaHook (Runner) {
167
230
  numSkippedSuites: skippedSuites.length,
168
231
  hasForcedToRunSuites: isForcedToRun,
169
232
  hasUnskippableSuites: !!unskippableSuites.length,
170
- error
233
+ error,
234
+ isEarlyFlakeDetectionEnabled
171
235
  })
172
236
  }))
173
237
 
@@ -191,7 +255,12 @@ function mochaHook (Runner) {
191
255
  const isUnskippable = unskippableSuites.includes(suite.file)
192
256
  isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
193
257
  asyncResource.runInAsyncScope(() => {
194
- testSuiteStartCh.publish({ testSuite: suite.file, isUnskippable, isForcedToRun })
258
+ testSuiteStartCh.publish({
259
+ testSuite: suite.file,
260
+ isUnskippable,
261
+ isForcedToRun,
262
+ itrCorrelationId
263
+ })
195
264
  })
196
265
  }
197
266
  })
@@ -247,8 +316,35 @@ function mochaHook (Runner) {
247
316
  const testStartLine = testToStartLine.get(test)
248
317
  const asyncResource = new AsyncResource('bound-anonymous-fn')
249
318
  testToAr.set(test.fn, asyncResource)
319
+
320
+ const {
321
+ file: testSuiteAbsolutePath,
322
+ title,
323
+ _ddIsNew: isNew,
324
+ _ddIsEfdRetry: isEfdRetry
325
+ } = test
326
+
327
+ const testInfo = {
328
+ testName: test.fullTitle(),
329
+ testSuiteAbsolutePath,
330
+ title,
331
+ isNew,
332
+ isEfdRetry,
333
+ testStartLine
334
+ }
335
+
336
+ // We want to store the result of the new tests
337
+ if (isNew) {
338
+ const testFullName = getTestFullName(test)
339
+ if (newTests[testFullName]) {
340
+ newTests[testFullName].push(test)
341
+ } else {
342
+ newTests[testFullName] = [test]
343
+ }
344
+ }
345
+
250
346
  asyncResource.runInAsyncScope(() => {
251
- testStartCh.publish({ test, testStartLine })
347
+ testStartCh.publish(testInfo)
252
348
  })
253
349
  })
254
350
 
@@ -317,10 +413,23 @@ function mochaHook (Runner) {
317
413
  })
318
414
 
319
415
  this.on('pending', (test) => {
416
+ const testStartLine = testToStartLine.get(test)
417
+ const {
418
+ file: testSuiteAbsolutePath,
419
+ title
420
+ } = test
421
+
422
+ const testInfo = {
423
+ testName: test.fullTitle(),
424
+ testSuiteAbsolutePath,
425
+ title,
426
+ testStartLine
427
+ }
428
+
320
429
  const asyncResource = getTestAsyncResource(test)
321
430
  if (asyncResource) {
322
431
  asyncResource.runInAsyncScope(() => {
323
- skipCh.publish(test)
432
+ skipCh.publish(testInfo)
324
433
  })
325
434
  } else {
326
435
  // if there is no async resource, the test has been skipped through `test.skip`
@@ -332,7 +441,7 @@ function mochaHook (Runner) {
332
441
  testToAr.set(test, skippedTestAsyncResource)
333
442
  }
334
443
  skippedTestAsyncResource.runInAsyncScope(() => {
335
- skipCh.publish(test)
444
+ skipCh.publish(testInfo)
336
445
  })
337
446
  }
338
447
  })
@@ -352,8 +461,8 @@ function mochaEachHook (mochaEach) {
352
461
  const [params] = arguments
353
462
  const { it, ...rest } = mochaEach.apply(this, arguments)
354
463
  return {
355
- it: function (name) {
356
- parameterizedTestCh.publish({ name, params })
464
+ it: function (title) {
465
+ parameterizedTestCh.publish({ title, params })
357
466
  it.apply(this, arguments)
358
467
  },
359
468
  ...rest
@@ -378,7 +487,7 @@ addHook({
378
487
  return run.apply(this, arguments)
379
488
  }
380
489
 
381
- if (!itrConfigurationCh.hasSubscribers || this.isWorker) {
490
+ if (!libraryConfigurationCh.hasSubscribers || this.isWorker) {
382
491
  if (this.isWorker) {
383
492
  isWorker = true
384
493
  }
@@ -395,11 +504,12 @@ addHook({
395
504
  }
396
505
  })
397
506
 
398
- const onReceivedSkippableSuites = ({ err, skippableSuites }) => {
507
+ const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
399
508
  if (err) {
400
509
  suitesToSkip = []
401
510
  } else {
402
511
  suitesToSkip = skippableSuites
512
+ itrCorrelationId = responseItrCorrelationId
403
513
  }
404
514
  // We remove the suites that we skip through ITR
405
515
  const filteredSuites = getFilteredSuites(runner.suite.suites)
@@ -418,21 +528,47 @@ addHook({
418
528
  global.run()
419
529
  }
420
530
 
421
- const onReceivedConfiguration = ({ err }) => {
531
+ const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
422
532
  if (err) {
423
- return global.run()
533
+ knownTests = []
534
+ isEarlyFlakeDetectionEnabled = false
535
+ } else {
536
+ knownTests = receivedKnownTests
537
+ }
538
+
539
+ if (isSuitesSkippingEnabled) {
540
+ skippableSuitesCh.publish({
541
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
542
+ })
543
+ } else {
544
+ global.run()
424
545
  }
425
- if (!skippableSuitesCh.hasSubscribers) {
546
+ }
547
+
548
+ const onReceivedConfiguration = ({ err, libraryConfig }) => {
549
+ if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
426
550
  return global.run()
427
551
  }
428
552
 
429
- skippableSuitesCh.publish({
430
- onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
431
- })
553
+ isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
554
+ isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
555
+ earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
556
+
557
+ if (isEarlyFlakeDetectionEnabled) {
558
+ knownTestsCh.publish({
559
+ onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
560
+ })
561
+ } else if (isSuitesSkippingEnabled) {
562
+ skippableSuitesCh.publish({
563
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
564
+ })
565
+ } else {
566
+ global.run()
567
+ }
432
568
  }
433
569
 
434
570
  mochaRunAsyncResource.runInAsyncScope(() => {
435
- itrConfigurationCh.publish({
571
+ libraryConfigurationCh.publish({
436
572
  onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
437
573
  })
438
574
  })
@@ -32,12 +32,18 @@ addHook({ name: 'mongodb', versions: ['>=4 <4.6.0'], file: 'lib/cmap/connection.
32
32
  return Connection
33
33
  })
34
34
 
35
- addHook({ name: 'mongodb', versions: ['>=4.6.0'], file: 'lib/cmap/connection.js' }, Connection => {
35
+ addHook({ name: 'mongodb', versions: ['>=4.6.0 <6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
36
36
  const proto = Connection.Connection.prototype
37
37
  shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command'))
38
38
  return Connection
39
39
  })
40
40
 
41
+ addHook({ name: 'mongodb', versions: ['>=6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
42
+ const proto = Connection.Connection.prototype
43
+ shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command', undefined, instrumentPromise))
44
+ return Connection
45
+ })
46
+
41
47
  addHook({ name: 'mongodb', versions: ['>=3.3 <4'], file: 'lib/core/wireprotocol/index.js' }, wp => wrapWp(wp))
42
48
 
43
49
  addHook({ name: 'mongodb-core', versions: ['>=3.2'], file: 'lib/wireprotocol/index.js' }, wp => wrapWp(wp))
@@ -89,7 +95,7 @@ function wrapUnifiedCommand (command, operation, name) {
89
95
  return shimmer.wrap(command, wrapped)
90
96
  }
91
97
 
92
- function wrapConnectionCommand (command, operation, name) {
98
+ function wrapConnectionCommand (command, operation, name, instrumentFn = instrument) {
93
99
  const wrapped = function (ns, ops) {
94
100
  if (!startCh.hasSubscribers) {
95
101
  return command.apply(this, arguments)
@@ -101,7 +107,7 @@ function wrapConnectionCommand (command, operation, name) {
101
107
  const topology = { s: { options } }
102
108
 
103
109
  ns = `${ns.db}.${ns.collection}`
104
- return instrument(operation, command, this, arguments, topology, ns, ops, { name })
110
+ return instrumentFn(operation, command, this, arguments, topology, ns, ops, { name })
105
111
  }
106
112
  return shimmer.wrap(command, wrapped)
107
113
  }
@@ -179,3 +185,28 @@ function instrument (operation, command, ctx, args, server, ns, ops, options = {
179
185
  }
180
186
  })
181
187
  }
188
+
189
+ function instrumentPromise (operation, command, ctx, args, server, ns, ops, options = {}) {
190
+ const name = options.name || (ops && Object.keys(ops)[0])
191
+
192
+ const serverInfo = server && server.s && server.s.options
193
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
194
+
195
+ return asyncResource.runInAsyncScope(() => {
196
+ startCh.publish({ ns, ops, options: serverInfo, name })
197
+
198
+ const promise = command.apply(ctx, args)
199
+
200
+ promise.then(function (res) {
201
+ finishCh.publish()
202
+ return res
203
+ }, function (err) {
204
+ errorCh.publish(err)
205
+ finishCh.publish()
206
+
207
+ return Promise.reject(err)
208
+ })
209
+
210
+ return promise
211
+ })
212
+ }
@@ -79,21 +79,26 @@ addHook({
79
79
  })
80
80
 
81
81
  let callbackWrapped = false
82
- const lastArgumentIndex = arguments.length - 1
83
82
 
84
- if (typeof arguments[lastArgumentIndex] === 'function') {
85
- // is a callback, wrap it to execute finish()
86
- shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
87
- return function () {
88
- finish()
83
+ const wrapCallbackIfExist = (args) => {
84
+ const lastArgumentIndex = args.length - 1
89
85
 
90
- return originalCb.apply(this, arguments)
91
- }
92
- })
86
+ if (typeof args[lastArgumentIndex] === 'function') {
87
+ // is a callback, wrap it to execute finish()
88
+ shimmer.wrap(args, lastArgumentIndex, originalCb => {
89
+ return function () {
90
+ finish()
91
+
92
+ return originalCb.apply(this, arguments)
93
+ }
94
+ })
93
95
 
94
- callbackWrapped = true
96
+ callbackWrapped = true
97
+ }
95
98
  }
96
99
 
100
+ wrapCallbackIfExist(arguments)
101
+
97
102
  return asyncResource.runInAsyncScope(() => {
98
103
  startCh.publish({
99
104
  filters,
@@ -106,8 +111,16 @@ addHook({
106
111
  if (!callbackWrapped) {
107
112
  shimmer.wrap(res, 'exec', originalExec => {
108
113
  return function wrappedExec () {
114
+ if (!callbackWrapped) {
115
+ wrapCallbackIfExist(arguments)
116
+ }
117
+
109
118
  const execResult = originalExec.apply(this, arguments)
110
119
 
120
+ if (callbackWrapped || typeof execResult?.then !== 'function') {
121
+ return execResult
122
+ }
123
+
111
124
  // wrap them method, wrap resolve and reject methods
112
125
  shimmer.wrap(execResult, 'then', originalThen => {
113
126
  return function wrappedThen () {
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const dc = require('dc-polyfill')
4
+ const {
5
+ channel,
6
+ addHook
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ const prepareCh = channel('datadog:mquery:filter:prepare')
11
+ const tracingCh = dc.tracingChannel('datadog:mquery:filter')
12
+
13
+ const methods = [
14
+ 'find',
15
+ 'findOne',
16
+ 'findOneAndRemove',
17
+ 'findOneAndDelete',
18
+ 'count',
19
+ 'distinct',
20
+ 'where'
21
+ ]
22
+
23
+ const methodsOptionalArgs = ['findOneAndUpdate']
24
+
25
+ function getFilters (args, methodName) {
26
+ const [arg0, arg1] = args
27
+
28
+ const filters = arg0 && typeof arg0 === 'object' ? [arg0] : []
29
+
30
+ if (arg1 && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
31
+ filters.push(arg1)
32
+ }
33
+
34
+ return filters
35
+ }
36
+
37
+ addHook({
38
+ name: 'mquery',
39
+ versions: ['>=5.0.0']
40
+ }, Query => {
41
+ [...methods, ...methodsOptionalArgs].forEach(methodName => {
42
+ if (!(methodName in Query.prototype)) return
43
+
44
+ shimmer.wrap(Query.prototype, methodName, method => {
45
+ return function () {
46
+ if (prepareCh.hasSubscribers) {
47
+ const filters = getFilters(arguments, methodName)
48
+ if (filters?.length) {
49
+ prepareCh.publish({ filters })
50
+ }
51
+ }
52
+
53
+ return method.apply(this, arguments)
54
+ }
55
+ })
56
+ })
57
+
58
+ shimmer.wrap(Query.prototype, 'exec', originalExec => {
59
+ return function wrappedExec () {
60
+ return tracingCh.tracePromise(originalExec, {}, this, arguments)
61
+ }
62
+ })
63
+
64
+ return Query
65
+ })
@@ -17,8 +17,16 @@ const errorTCPCh = channel('apm:net:tcp:error')
17
17
 
18
18
  const connectionCh = channel(`apm:net:tcp:connection`)
19
19
 
20
- addHook({ name: 'net' }, net => {
21
- require('dns')
20
+ const names = ['net', 'node:net']
21
+
22
+ addHook({ name: names }, (net, version, name) => {
23
+ // explicitly require dns so that net gets an instrumented instance
24
+ // so that we don't miss the dns calls
25
+ if (name === 'net') {
26
+ require('dns')
27
+ } else {
28
+ require('node:dns')
29
+ }
22
30
 
23
31
  shimmer.wrap(net.Socket.prototype, 'connect', connect => function () {
24
32
  if (!startICPCh.hasSubscribers || !startTCPCh.hasSubscribers) {
@@ -65,7 +65,7 @@ function wrapRenderToHTML (renderToHTML) {
65
65
 
66
66
  function wrapRenderErrorToHTML (renderErrorToHTML) {
67
67
  return function (err, req, res, pathname, query) {
68
- return instrument(req, res, () => renderErrorToHTML.apply(this, arguments))
68
+ return instrument(req, res, err, () => renderErrorToHTML.apply(this, arguments))
69
69
  }
70
70
  }
71
71
 
@@ -76,8 +76,8 @@ function wrapRenderToResponse (renderToResponse) {
76
76
  }
77
77
 
78
78
  function wrapRenderErrorToResponse (renderErrorToResponse) {
79
- return function (ctx) {
80
- return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
79
+ return function (ctx, err) {
80
+ return instrument(ctx.req, ctx.res, err, () => renderErrorToResponse.apply(this, arguments))
81
81
  }
82
82
  }
83
83
 
@@ -111,13 +111,23 @@ function getPageFromPath (page, dynamicRoutes = []) {
111
111
  return getPagePath(page)
112
112
  }
113
113
 
114
- function instrument (req, res, handler) {
114
+ function instrument (req, res, error, handler) {
115
+ if (typeof error === 'function') {
116
+ handler = error
117
+ error = null
118
+ }
119
+
115
120
  req = req.originalRequest || req
116
121
  res = res.originalResponse || res
117
122
 
118
123
  // TODO support middleware properly in the future?
119
124
  const isMiddleware = req.headers[MIDDLEWARE_HEADER]
120
- if (isMiddleware || requests.has(req)) return handler()
125
+ if (isMiddleware || requests.has(req)) {
126
+ if (error) {
127
+ errorChannel.publish({ error })
128
+ }
129
+ return handler()
130
+ }
121
131
 
122
132
  requests.add(req)
123
133
 
@@ -144,7 +154,9 @@ function instrument (req, res, handler) {
144
154
  function wrapServeStatic (serveStatic) {
145
155
  return function (req, res, path) {
146
156
  return instrument(req, res, () => {
147
- if (pageLoadChannel.hasSubscribers && path) pageLoadChannel.publish({ page: path })
157
+ if (pageLoadChannel.hasSubscribers && path) {
158
+ pageLoadChannel.publish({ page: path, isStatic: true })
159
+ }
148
160
 
149
161
  return serveStatic.apply(this, arguments)
150
162
  })
@@ -278,9 +290,23 @@ addHook({
278
290
  shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
279
291
  return async function wrappedJson () {
280
292
  const body = await originalMethod.apply(this, arguments)
281
- bodyParsedChannel.publish({
282
- body
283
- })
293
+
294
+ bodyParsedChannel.publish({ body })
295
+
296
+ return body
297
+ }
298
+ })
299
+
300
+ shimmer.wrap(request.NextRequest.prototype, 'formData', function (originalFormData) {
301
+ return async function wrappedFormData () {
302
+ const body = await originalFormData.apply(this, arguments)
303
+
304
+ let normalizedBody = body
305
+ if (typeof body.entries === 'function') {
306
+ normalizedBody = Object.fromEntries(body.entries())
307
+ }
308
+ bodyParsedChannel.publish({ body: normalizedBody })
309
+
284
310
  return body
285
311
  }
286
312
  })