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
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const pick = require('lodash.pick')
3
+ const pick = require('../../datadog-core/src/utils/src/pick')
4
4
  const log = require('../../dd-trace/src/log')
5
5
 
6
6
  module.exports = {
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
58
58
  span._spanContext._trace.record = false
59
59
  }
60
60
 
61
- if (!(hasAmazonSignature(options) || !this.config.propagationFilter(uri))) {
61
+ if (this.shouldInjectTraceHeaders(options, uri)) {
62
62
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
63
  }
64
64
 
@@ -71,6 +71,18 @@ class HttpClientPlugin extends ClientPlugin {
71
71
  return message.currentStore
72
72
  }
73
73
 
74
+ shouldInjectTraceHeaders (options, uri) {
75
+ if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
+ return false
77
+ }
78
+
79
+ if (!this.config.propagationFilter(uri)) {
80
+ return false
81
+ }
82
+
83
+ return true
84
+ }
85
+
74
86
  bindAsyncStart ({ parentStore }) {
75
87
  return parentStore
76
88
  }
@@ -98,7 +110,7 @@ class HttpClientPlugin extends ClientPlugin {
98
110
  span.finish()
99
111
  }
100
112
 
101
- error ({ span, error }) {
113
+ error ({ span, error, args, customRequestTimeout }) {
102
114
  if (!span) return
103
115
  if (error) {
104
116
  span.addTags({
@@ -107,6 +119,11 @@ class HttpClientPlugin extends ClientPlugin {
107
119
  [ERROR_STACK]: error.stack
108
120
  })
109
121
  } else {
122
+ // conditions for no error:
123
+ // 1. not using a custom agent instance with custom timeout specified
124
+ // 2. no invocation of `req.setTimeout`
125
+ if (!args.options.agent?.options?.timeout && !customRequestTimeout) return
126
+
110
127
  span.setTag('error', 1)
111
128
  }
112
129
  }
@@ -12,10 +12,27 @@ const {
12
12
  TEST_FRAMEWORK_VERSION,
13
13
  TEST_SOURCE_START,
14
14
  TEST_ITR_UNSKIPPABLE,
15
- TEST_ITR_FORCED_RUN
15
+ TEST_ITR_FORCED_RUN,
16
+ TEST_CODE_OWNERS,
17
+ ITR_CORRELATION_ID,
18
+ TEST_SOURCE_FILE,
19
+ TEST_IS_NEW,
20
+ TEST_EARLY_FLAKE_IS_RETRY,
21
+ TEST_EARLY_FLAKE_IS_ENABLED,
22
+ JEST_DISPLAY_NAME
16
23
  } = require('../../dd-trace/src/plugins/util/test')
17
24
  const { COMPONENT } = require('../../dd-trace/src/constants')
18
25
  const id = require('../../dd-trace/src/id')
26
+ const {
27
+ TELEMETRY_EVENT_CREATED,
28
+ TELEMETRY_EVENT_FINISHED,
29
+ TELEMETRY_CODE_COVERAGE_STARTED,
30
+ TELEMETRY_CODE_COVERAGE_FINISHED,
31
+ TELEMETRY_ITR_FORCED_TO_RUN,
32
+ TELEMETRY_CODE_COVERAGE_EMPTY,
33
+ TELEMETRY_ITR_UNSKIPPABLE,
34
+ TELEMETRY_CODE_COVERAGE_NUM_FILES
35
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
19
36
 
20
37
  const isJestWorker = !!process.env.JEST_WORKER_ID
21
38
 
@@ -27,6 +44,20 @@ class JestPlugin extends CiPlugin {
27
44
  return 'jest'
28
45
  }
29
46
 
47
+ // The lists are the same for every test suite, so we can cache them
48
+ getUnskippableSuites (unskippableSuitesList) {
49
+ if (!this.unskippableSuites) {
50
+ this.unskippableSuites = JSON.parse(unskippableSuitesList)
51
+ }
52
+ return this.unskippableSuites
53
+ }
54
+ getForcedToRunSuites (forcedToRunSuitesList) {
55
+ if (!this.forcedToRunSuites) {
56
+ this.forcedToRunSuites = JSON.parse(forcedToRunSuitesList)
57
+ }
58
+ return this.forcedToRunSuites
59
+ }
60
+
30
61
  constructor (...args) {
31
62
  super(...args)
32
63
 
@@ -55,7 +86,9 @@ class JestPlugin extends CiPlugin {
55
86
  numSkippedSuites,
56
87
  hasUnskippableSuites,
57
88
  hasForcedToRunSuites,
58
- error
89
+ error,
90
+ isEarlyFlakeDetectionEnabled,
91
+ onDone
59
92
  }) => {
60
93
  this.testSessionSpan.setTag(TEST_STATUS, status)
61
94
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -80,30 +113,47 @@ class JestPlugin extends CiPlugin {
80
113
  }
81
114
  )
82
115
 
116
+ if (isEarlyFlakeDetectionEnabled) {
117
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
118
+ }
119
+
83
120
  this.testModuleSpan.finish()
121
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
84
122
  this.testSessionSpan.finish()
123
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
85
124
  finishAllTraceSpans(this.testSessionSpan)
86
- this.tracer._exporter.flush()
125
+
126
+ this.tracer._exporter.flush(() => {
127
+ if (onDone) {
128
+ onDone()
129
+ }
130
+ })
87
131
  })
88
132
 
89
133
  // Test suites can be run in a different process from jest's main one.
90
134
  // This subscriber changes the configuration objects from jest to inject the trace id
91
- // of the test session to the processes that run the test suites.
135
+ // of the test session to the processes that run the test suites, and other data.
92
136
  this.addSub('ci:jest:session:configuration', configs => {
93
137
  configs.forEach(config => {
94
138
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
95
139
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
96
140
  config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
141
+ config._ddItrCorrelationId = this.itrCorrelationId
142
+ config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
143
+ config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
144
+ config._ddRepositoryRoot = this.repositoryRoot
97
145
  })
98
146
  })
99
147
 
100
- this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions, frameworkVersion }) => {
148
+ this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions, frameworkVersion, displayName }) => {
101
149
  const {
102
150
  _ddTestSessionId: testSessionId,
103
151
  _ddTestCommand: testCommand,
104
152
  _ddTestModuleId: testModuleId,
153
+ _ddItrCorrelationId: itrCorrelationId,
105
154
  _ddForcedToRun,
106
- _ddUnskippable
155
+ _ddUnskippable,
156
+ _ddTestCodeCoverageEnabled
107
157
  } = testEnvironmentOptions
108
158
 
109
159
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -114,11 +164,25 @@ class JestPlugin extends CiPlugin {
114
164
  const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
115
165
 
116
166
  if (_ddUnskippable) {
117
- testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
167
+ const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
168
+ if (unskippableSuites[testSuite]) {
169
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
170
+ testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
171
+ }
118
172
  if (_ddForcedToRun) {
119
- testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
173
+ const forcedToRunSuites = this.getForcedToRunSuites(_ddForcedToRun)
174
+ if (forcedToRunSuites[testSuite]) {
175
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
176
+ testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
177
+ }
120
178
  }
121
179
  }
180
+ if (itrCorrelationId) {
181
+ testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
182
+ }
183
+ if (displayName) {
184
+ testSuiteMetadata[JEST_DISPLAY_NAME] = displayName
185
+ }
122
186
 
123
187
  this.testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
124
188
  childOf: testSessionSpanContext,
@@ -128,6 +192,10 @@ class JestPlugin extends CiPlugin {
128
192
  ...testSuiteMetadata
129
193
  }
130
194
  })
195
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
196
+ if (_ddTestCodeCoverageEnabled) {
197
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
198
+ }
131
199
  })
132
200
 
133
201
  this.addSub('ci:jest:worker-report:trace', traces => {
@@ -164,6 +232,7 @@ class JestPlugin extends CiPlugin {
164
232
  this.testSuiteSpan.setTag('error', new Error(errorMessage))
165
233
  }
166
234
  this.testSuiteSpan.finish()
235
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
167
236
  // Suites potentially run in a different process than the session,
168
237
  // so calling finishAllTraceSpans on the session span is not enough
169
238
  finishAllTraceSpans(this.testSuiteSpan)
@@ -176,18 +245,26 @@ class JestPlugin extends CiPlugin {
176
245
  })
177
246
 
178
247
  /**
179
- * This can't use `this.itrConfig` like `ci:mocha:test-suite:code-coverage`
248
+ * This can't use `this.libraryConfig` like `ci:mocha:test-suite:code-coverage`
180
249
  * because this subscription happens in a different process from the one
181
250
  * fetching the ITR config.
182
251
  */
183
- this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
252
+ this.addSub('ci:jest:test-suite:code-coverage', ({ coverageFiles, testSuite }) => {
253
+ if (!coverageFiles.length) {
254
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
255
+ }
256
+ const files = [...coverageFiles, testSuite]
257
+
184
258
  const { _traceId, _spanId } = this.testSuiteSpan.context()
185
259
  const formattedCoverage = {
186
260
  sessionId: _traceId,
187
261
  suiteId: _spanId,
188
- files: coverageFiles
262
+ files
189
263
  }
264
+
190
265
  this.tracer._exporter.exportCoverage(formattedCoverage)
266
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
267
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, files.length)
191
268
  })
192
269
 
193
270
  this.addSub('ci:jest:test:start', (test) => {
@@ -204,6 +281,11 @@ class JestPlugin extends CiPlugin {
204
281
  span.setTag(TEST_SOURCE_START, testStartLine)
205
282
  }
206
283
  span.finish()
284
+ this.telemetry.ciVisEvent(
285
+ TELEMETRY_EVENT_FINISHED,
286
+ 'test',
287
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
288
+ )
207
289
  finishAllTraceSpans(span)
208
290
  })
209
291
 
@@ -226,7 +308,18 @@ class JestPlugin extends CiPlugin {
226
308
  }
227
309
 
228
310
  startTestSpan (test) {
229
- const { suite, name, runner, testParameters, frameworkVersion, testStartLine } = test
311
+ const {
312
+ suite,
313
+ name,
314
+ runner,
315
+ displayName,
316
+ testParameters,
317
+ frameworkVersion,
318
+ testStartLine,
319
+ testSourceFile,
320
+ isNew,
321
+ isEfdRetry
322
+ } = test
230
323
 
231
324
  const extraTags = {
232
325
  [JEST_TEST_RUNNER]: runner,
@@ -236,6 +329,19 @@ class JestPlugin extends CiPlugin {
236
329
  if (testStartLine) {
237
330
  extraTags[TEST_SOURCE_START] = testStartLine
238
331
  }
332
+ // If for whatever we don't have the source file, we'll fall back to the suite name
333
+ extraTags[TEST_SOURCE_FILE] = testSourceFile || suite
334
+
335
+ if (displayName) {
336
+ extraTags[JEST_DISPLAY_NAME] = displayName
337
+ }
338
+
339
+ if (isNew) {
340
+ extraTags[TEST_IS_NEW] = 'true'
341
+ if (isEfdRetry) {
342
+ extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true'
343
+ }
344
+ }
239
345
 
240
346
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
241
347
  }
@@ -77,30 +77,52 @@ function isMarkedAsUnskippable (test) {
77
77
  }
78
78
 
79
79
  function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
80
- return originalTests.reduce((acc, test) => {
80
+ const unskippableSuites = {}
81
+ const forcedToRunSuites = {}
82
+
83
+ const skippedSuites = []
84
+ const suitesToRun = []
85
+
86
+ for (const test of originalTests) {
81
87
  const relativePath = getTestSuitePath(test.path, rootDir)
82
88
  const shouldBeSkipped = skippableSuites.includes(relativePath)
83
-
84
89
  if (isMarkedAsUnskippable(test)) {
85
- acc.suitesToRun.push(test)
86
- if (test?.context?.config?.testEnvironmentOptions) {
87
- test.context.config.testEnvironmentOptions['_ddUnskippable'] = true
88
- acc.hasUnskippableSuites = true
89
- if (shouldBeSkipped) {
90
- test.context.config.testEnvironmentOptions['_ddForcedToRun'] = true
91
- acc.hasForcedToRunSuites = true
92
- }
90
+ suitesToRun.push(test)
91
+ unskippableSuites[relativePath] = true
92
+ if (shouldBeSkipped) {
93
+ forcedToRunSuites[relativePath] = true
93
94
  }
94
- return acc
95
+ continue
95
96
  }
96
-
97
97
  if (shouldBeSkipped) {
98
- acc.skippedSuites.push(relativePath)
98
+ skippedSuites.push(relativePath)
99
99
  } else {
100
- acc.suitesToRun.push(test)
100
+ suitesToRun.push(test)
101
101
  }
102
- return acc
103
- }, { skippedSuites: [], suitesToRun: [], hasUnskippableSuites: false, hasForcedToRunSuites: false })
102
+ }
103
+
104
+ const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
105
+ const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
106
+
107
+ if (originalTests.length) {
108
+ // The config object is shared by all tests, so we can just take the first one
109
+ const [test] = originalTests
110
+ if (test?.context?.config?.testEnvironmentOptions) {
111
+ if (hasUnskippableSuites) {
112
+ test.context.config.testEnvironmentOptions._ddUnskippable = JSON.stringify(unskippableSuites)
113
+ }
114
+ if (hasForcedToRunSuites) {
115
+ test.context.config.testEnvironmentOptions._ddForcedToRun = JSON.stringify(forcedToRunSuites)
116
+ }
117
+ }
118
+ }
119
+
120
+ return {
121
+ skippedSuites,
122
+ suitesToRun,
123
+ hasUnskippableSuites,
124
+ hasForcedToRunSuites
125
+ }
104
126
  }
105
127
 
106
128
  module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }
@@ -1,19 +1,71 @@
1
1
  'use strict'
2
2
 
3
+ const dc = require('dc-polyfill')
4
+ const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
5
+ const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
3
6
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
7
 
8
+ const afterStartCh = dc.channel('dd-trace:kafkajs:consumer:afterStart')
9
+ const beforeFinishCh = dc.channel('dd-trace:kafkajs:consumer:beforeFinish')
10
+
5
11
  class KafkajsConsumerPlugin extends ConsumerPlugin {
6
12
  static get id () { return 'kafkajs' }
7
13
  static get operation () { return 'consume' }
8
14
 
9
- start ({ topic, partition, message, groupId }) {
10
- if (this.config.dsmEnabled) {
11
- this.tracer.decodeDataStreamsContext(message.headers['dd-pathway-ctx'])
12
- this.tracer
13
- .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'])
15
+ constructor () {
16
+ super(...arguments)
17
+ this.addSub('apm:kafkajs:consume:commit', message => this.commit(message))
18
+ }
19
+
20
+ /**
21
+ * Transform individual commit details sent by kafkajs' event reporter
22
+ * into actionable backlog items for DSM
23
+ *
24
+ * @typedef {object} ConsumerBacklog
25
+ * @property {number} type
26
+ * @property {string} consumer_group
27
+ * @property {string} topic
28
+ * @property {number} partition
29
+ * @property {number} offset
30
+ *
31
+ * @typedef {object} CommitEventItem
32
+ * @property {string} groupId
33
+ * @property {string} topic
34
+ * @property {number} partition
35
+ * @property {import('kafkajs/utils/long').Long} offset
36
+ *
37
+ * @param {CommitEventItem} commit
38
+ * @returns {ConsumerBacklog}
39
+ */
40
+ transformCommit (commit) {
41
+ const { groupId, partition, offset, topic } = commit
42
+ return {
43
+ partition,
44
+ topic,
45
+ type: 'kafka_commit',
46
+ offset: Number(offset),
47
+ consumer_group: groupId
14
48
  }
49
+ }
50
+
51
+ commit (commitList) {
52
+ if (!this.config.dsmEnabled) return
53
+ const keys = [
54
+ 'consumer_group',
55
+ 'type',
56
+ 'partition',
57
+ 'offset',
58
+ 'topic'
59
+ ]
60
+ for (const commit of commitList.map(this.transformCommit)) {
61
+ if (keys.some(key => !commit.hasOwnProperty(key))) continue
62
+ this.tracer.setOffset(commit)
63
+ }
64
+ }
65
+
66
+ start ({ topic, partition, message, groupId }) {
15
67
  const childOf = extract(this.tracer, message.headers)
16
- this.startSpan({
68
+ const span = this.startSpan({
17
69
  childOf,
18
70
  resource: topic,
19
71
  type: 'worker',
@@ -26,6 +78,24 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
26
78
  'kafka.partition': partition
27
79
  }
28
80
  })
81
+ if (this.config.dsmEnabled && message?.headers && DsmPathwayCodec.contextExists(message.headers)) {
82
+ const payloadSize = getMessageSize(message)
83
+ this.tracer.decodeDataStreamsContext(message.headers)
84
+ this.tracer
85
+ .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], span, payloadSize)
86
+ }
87
+
88
+ if (afterStartCh.hasSubscribers) {
89
+ afterStartCh.publish({ topic, partition, message, groupId })
90
+ }
91
+ }
92
+
93
+ finish () {
94
+ if (beforeFinishCh.hasSubscribers) {
95
+ beforeFinishCh.publish()
96
+ }
97
+
98
+ super.finish()
29
99
  }
30
100
  }
31
101
 
@@ -1,7 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
- const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
4
+ const { DsmPathwayCodec } = require('../../dd-trace/src/datastreams/pathway')
5
+ const { getMessageSize } = require('../../dd-trace/src/datastreams/processor')
6
+
5
7
  const BOOTSTRAP_SERVERS_KEY = 'messaging.kafka.bootstrap.servers'
6
8
 
7
9
  class KafkajsProducerPlugin extends ProducerPlugin {
@@ -9,13 +11,62 @@ class KafkajsProducerPlugin extends ProducerPlugin {
9
11
  static get operation () { return 'produce' }
10
12
  static get peerServicePrecursors () { return [BOOTSTRAP_SERVERS_KEY] }
11
13
 
12
- start ({ topic, messages, bootstrapServers }) {
13
- let pathwayCtx
14
- if (this.config.dsmEnabled) {
15
- const dataStreamsContext = this.tracer
16
- .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'])
17
- pathwayCtx = encodePathwayContext(dataStreamsContext)
14
+ constructor () {
15
+ super(...arguments)
16
+ this.addSub('apm:kafkajs:produce:commit', message => this.commit(message))
17
+ }
18
+
19
+ /**
20
+ * Transform individual commit details sent by kafkajs' event reporter
21
+ * into actionable backlog items for DSM
22
+ *
23
+ * @typedef {object} ProducerBacklog
24
+ * @property {number} type
25
+ * @property {string} topic
26
+ * @property {number} partition
27
+ * @property {number} offset
28
+ *
29
+ * @typedef {object} ProducerResponseItem
30
+ * @property {string} topic
31
+ * @property {number} partition
32
+ * @property {import('kafkajs/utils/long').Long} [offset]
33
+ * @property {import('kafkajs/utils/long').Long} [baseOffset]
34
+ *
35
+ * @param {ProducerResponseItem} response
36
+ * @returns {ProducerBacklog}
37
+ */
38
+ transformProduceResponse (response) {
39
+ // In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
40
+ const { topicName: topic, partition, offset, baseOffset } = response
41
+ const offsetAsLong = offset || baseOffset
42
+ return {
43
+ type: 'kafka_produce',
44
+ partition,
45
+ offset: offsetAsLong ? Number(offsetAsLong) : undefined,
46
+ topic
47
+ }
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param {ProducerResponseItem[]} commitList
53
+ * @returns {void}
54
+ */
55
+ commit (commitList) {
56
+ if (!this.config.dsmEnabled) return
57
+ const keys = [
58
+ 'type',
59
+ 'partition',
60
+ 'offset',
61
+ 'topic'
62
+ ]
63
+ for (const commit of commitList.map(this.transformProduceResponse)) {
64
+ if (keys.some(key => !commit.hasOwnProperty(key))) continue
65
+ this.tracer.setOffset(commit)
18
66
  }
67
+ }
68
+
69
+ start ({ topic, messages, bootstrapServers }) {
19
70
  const span = this.startSpan({
20
71
  resource: topic,
21
72
  meta: {
@@ -31,8 +82,13 @@ class KafkajsProducerPlugin extends ProducerPlugin {
31
82
  }
32
83
  for (const message of messages) {
33
84
  if (typeof message === 'object') {
34
- if (this.config.dsmEnabled) message.headers['dd-pathway-ctx'] = pathwayCtx
35
85
  this.tracer.inject(span, 'text_map', message.headers)
86
+ if (this.config.dsmEnabled) {
87
+ const payloadSize = getMessageSize(message)
88
+ const dataStreamsContext = this.tracer
89
+ .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'], span, payloadSize)
90
+ DsmPathwayCodec.encode(dataStreamsContext, message.headers)
91
+ }
36
92
  }
37
93
  }
38
94
  }