dd-trace 4.18.0 → 4.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/LICENSE-3rdparty.csv +3 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +29 -0
  8. package/package.json +12 -11
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  13. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  14. package/packages/datadog-instrumentations/src/child-process.js +4 -5
  15. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  16. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  17. package/packages/datadog-instrumentations/src/dns.js +2 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  19. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +10 -2
  21. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  22. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  23. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  24. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  25. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  26. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  27. package/packages/datadog-instrumentations/src/jest.js +12 -6
  28. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  29. package/packages/datadog-instrumentations/src/net.js +10 -2
  30. package/packages/datadog-instrumentations/src/next.js +18 -6
  31. package/packages/datadog-instrumentations/src/restify.js +14 -1
  32. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  33. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  34. package/packages/datadog-plugin-cucumber/src/index.js +34 -2
  35. package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
  36. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  37. package/packages/datadog-plugin-http/src/client.js +19 -2
  38. package/packages/datadog-plugin-jest/src/index.js +38 -4
  39. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  40. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  41. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  42. package/packages/datadog-plugin-next/src/index.js +40 -14
  43. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  44. package/packages/dd-trace/src/appsec/activation.js +29 -0
  45. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  46. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  48. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  49. package/packages/dd-trace/src/appsec/channels.js +5 -2
  50. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  51. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  52. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  53. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  54. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  55. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  56. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  59. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  64. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  65. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  66. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  67. package/packages/dd-trace/src/appsec/index.js +33 -32
  68. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  69. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  70. package/packages/dd-trace/src/appsec/remote_config/index.js +40 -15
  71. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  72. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  73. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  74. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  75. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  78. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +110 -59
  80. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +40 -7
  81. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  82. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  83. package/packages/dd-trace/src/config.js +145 -63
  84. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  85. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  86. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  87. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  88. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  89. package/packages/dd-trace/src/format.js +6 -1
  90. package/packages/dd-trace/src/id.js +12 -0
  91. package/packages/dd-trace/src/iitm.js +1 -1
  92. package/packages/dd-trace/src/log/channels.js +1 -1
  93. package/packages/dd-trace/src/noop/proxy.js +4 -0
  94. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  95. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  96. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  97. package/packages/dd-trace/src/opentracing/span.js +6 -0
  98. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  99. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  100. package/packages/dd-trace/src/plugin_manager.js +1 -1
  101. package/packages/dd-trace/src/plugins/ci_plugin.js +46 -9
  102. package/packages/dd-trace/src/plugins/database.js +1 -1
  103. package/packages/dd-trace/src/plugins/index.js +6 -0
  104. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  105. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  106. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  107. package/packages/dd-trace/src/plugins/util/git.js +98 -22
  108. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  109. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  110. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  111. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  112. package/packages/dd-trace/src/priority_sampler.js +30 -38
  113. package/packages/dd-trace/src/profiler.js +5 -3
  114. package/packages/dd-trace/src/profiling/config.js +26 -2
  115. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  116. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  117. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  118. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  119. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  121. package/packages/dd-trace/src/proxy.js +25 -1
  122. package/packages/dd-trace/src/ritm.js +1 -1
  123. package/packages/dd-trace/src/sampling_rule.js +130 -0
  124. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  125. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  126. package/packages/dd-trace/src/span_processor.js +4 -0
  127. package/packages/dd-trace/src/span_sampler.js +6 -64
  128. package/packages/dd-trace/src/spanleak.js +98 -0
  129. package/packages/dd-trace/src/startup-log.js +7 -1
  130. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  131. package/packages/dd-trace/src/telemetry/index.js +171 -41
  132. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  133. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  134. package/packages/dd-trace/src/tracer.js +8 -2
  135. package/scripts/install_plugin_modules.js +11 -3
  136. package/packages/diagnostics_channel/index.js +0 -3
  137. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -29,6 +29,29 @@ const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
29
29
  const log = require('../../dd-trace/src/log')
30
30
  const NoopTracer = require('../../dd-trace/src/noop/tracer')
31
31
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
32
+ const {
33
+ TELEMETRY_EVENT_CREATED,
34
+ TELEMETRY_EVENT_FINISHED,
35
+ TELEMETRY_ITR_FORCED_TO_RUN,
36
+ TELEMETRY_CODE_COVERAGE_EMPTY,
37
+ TELEMETRY_ITR_UNSKIPPABLE,
38
+ TELEMETRY_CODE_COVERAGE_NUM_FILES,
39
+ incrementCountMetric,
40
+ distributionMetric
41
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
42
+ const {
43
+ GIT_REPOSITORY_URL,
44
+ GIT_COMMIT_SHA,
45
+ GIT_BRANCH,
46
+ CI_PROVIDER_NAME
47
+ } = require('../../dd-trace/src/plugins/util/tags')
48
+ const {
49
+ OS_VERSION,
50
+ OS_PLATFORM,
51
+ OS_ARCHITECTURE,
52
+ RUNTIME_NAME,
53
+ RUNTIME_VERSION
54
+ } = require('../../dd-trace/src/plugins/util/env')
32
55
 
33
56
  const TEST_FRAMEWORK_NAME = 'cypress'
34
57
 
@@ -152,16 +175,19 @@ module.exports = (on, config) => {
152
175
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
153
176
 
154
177
  const {
155
- 'git.repository_url': repositoryUrl,
156
- 'git.commit.sha': sha,
157
- 'os.version': osVersion,
158
- 'os.platform': osPlatform,
159
- 'os.architecture': osArchitecture,
160
- 'runtime.name': runtimeName,
161
- 'runtime.version': runtimeVersion,
162
- 'git.branch': branch
178
+ [GIT_REPOSITORY_URL]: repositoryUrl,
179
+ [GIT_COMMIT_SHA]: sha,
180
+ [OS_VERSION]: osVersion,
181
+ [OS_PLATFORM]: osPlatform,
182
+ [OS_ARCHITECTURE]: osArchitecture,
183
+ [RUNTIME_NAME]: runtimeName,
184
+ [RUNTIME_VERSION]: runtimeVersion,
185
+ [GIT_BRANCH]: branch,
186
+ [CI_PROVIDER_NAME]: ciProviderName
163
187
  } = testEnvironmentMetadata
164
188
 
189
+ const isUnsupportedCIProvider = !ciProviderName
190
+
165
191
  const finishedTestsByFile = {}
166
192
 
167
193
  const testConfiguration = {
@@ -192,6 +218,15 @@ module.exports = (on, config) => {
192
218
  let hasForcedToRunSuites = false
193
219
  let hasUnskippableSuites = false
194
220
 
221
+ function ciVisEvent (name, testLevel, tags = {}) {
222
+ incrementCountMetric(name, {
223
+ testLevel,
224
+ testFramework: 'cypress',
225
+ isUnsupportedCIProvider,
226
+ ...tags
227
+ })
228
+ }
229
+
195
230
  function getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
196
231
  const testSuiteTags = {
197
232
  [TEST_COMMAND]: command,
@@ -220,14 +255,18 @@ module.exports = (on, config) => {
220
255
 
221
256
  if (isUnskippable) {
222
257
  hasUnskippableSuites = true
258
+ incrementCountMetric(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
223
259
  testSpanMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
224
260
  }
225
261
 
226
262
  if (isForcedToRun) {
227
263
  hasForcedToRunSuites = true
264
+ incrementCountMetric(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
228
265
  testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
229
266
  }
230
267
 
268
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'test', { hasCodeOwners: !!codeOwners })
269
+
231
270
  return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
232
271
  childOf,
233
272
  tags: {
@@ -281,6 +320,8 @@ module.exports = (on, config) => {
281
320
  ...testSessionSpanMetadata
282
321
  }
283
322
  })
323
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
324
+
284
325
  testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
285
326
  childOf: testSessionSpan,
286
327
  tags: {
@@ -289,6 +330,8 @@ module.exports = (on, config) => {
289
330
  ...testModuleSpanMetadata
290
331
  }
291
332
  })
333
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
334
+
292
335
  return details
293
336
  })
294
337
  })
@@ -347,6 +390,7 @@ module.exports = (on, config) => {
347
390
  }
348
391
  testSuiteSpan.finish()
349
392
  testSuiteSpan = null
393
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
350
394
  }
351
395
  })
352
396
 
@@ -371,7 +415,9 @@ module.exports = (on, config) => {
371
415
  )
372
416
 
373
417
  testModuleSpan.finish()
418
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
374
419
  testSessionSpan.finish()
420
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
375
421
 
376
422
  finishAllTraceSpans(testSessionSpan)
377
423
  }
@@ -406,6 +452,7 @@ module.exports = (on, config) => {
406
452
  ...testSuiteSpanMetadata
407
453
  }
408
454
  })
455
+ ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
409
456
  return null
410
457
  },
411
458
  'dd:beforeEach': (test) => {
@@ -435,6 +482,10 @@ module.exports = (on, config) => {
435
482
  if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
436
483
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
437
484
  const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
485
+ if (!relativeCoverageFiles.length) {
486
+ incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
487
+ }
488
+ distributionMetric(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
438
489
  const { _traceId, _spanId } = testSuiteSpan.context()
439
490
  const formattedCoverage = {
440
491
  sessionId: _traceId,
@@ -470,6 +521,7 @@ module.exports = (on, config) => {
470
521
  // test spans are finished at after:spec
471
522
  }
472
523
  activeSpan = null
524
+ ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test')
473
525
  return null
474
526
  },
475
527
  'dd:addTags': (tags) => {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const dc = require('dc-polyfill')
4
5
 
5
6
  const collapsedPathSym = Symbol('collapsedPaths')
6
7
 
@@ -14,8 +15,6 @@ class GraphQLResolvePlugin extends TracingPlugin {
14
15
  if (!shouldInstrument(this.config, path)) return
15
16
  const computedPathString = path.join('.')
16
17
 
17
- addResolver(context, info, args)
18
-
19
18
  if (this.config.collapse) {
20
19
  if (!context[collapsedPathSym]) {
21
20
  context[collapsedPathSym] = {}
@@ -55,6 +54,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
55
54
  span.setTag(`graphql.variables.${name}`, variables[name])
56
55
  })
57
56
  }
57
+
58
+ if (this.resolverStartCh.hasSubscribers) {
59
+ this.resolverStartCh.publish({ context, resolverInfo: getResolverInfo(info, args) })
60
+ }
58
61
  }
59
62
 
60
63
  constructor (...args) {
@@ -69,6 +72,8 @@ class GraphQLResolvePlugin extends TracingPlugin {
69
72
  field.finishTime = span._getTime ? span._getTime() : 0
70
73
  field.error = field.error || err
71
74
  })
75
+
76
+ this.resolverStartCh = dc.channel('datadog:graphql:resolver:start')
72
77
  }
73
78
 
74
79
  configure (config) {
@@ -109,28 +114,31 @@ function withCollapse (responsePathAsArray) {
109
114
  }
110
115
  }
111
116
 
112
- function addResolver (context, info, args) {
113
- if (info.rootValue && !info.rootValue[info.fieldName]) {
114
- return
115
- }
117
+ function getResolverInfo (info, args) {
118
+ let resolverInfo = null
119
+ const resolverVars = {}
116
120
 
117
- if (!context.resolvers) {
118
- context.resolvers = {}
121
+ if (args && Object.keys(args).length) {
122
+ Object.assign(resolverVars, args)
119
123
  }
120
124
 
121
- const resolvers = context.resolvers
122
-
123
- if (!resolvers[info.fieldName]) {
124
- if (args && Object.keys(args).length) {
125
- resolvers[info.fieldName] = [args]
126
- } else {
127
- resolvers[info.fieldName] = []
125
+ const directives = info.fieldNodes[0].directives
126
+ for (const directive of directives) {
127
+ const argList = {}
128
+ for (const argument of directive['arguments']) {
129
+ argList[argument.name.value] = argument.value.value
128
130
  }
129
- } else {
130
- if (args && Object.keys(args).length) {
131
- resolvers[info.fieldName].push(args)
131
+
132
+ if (Object.keys(argList).length) {
133
+ resolverVars[directive.name.value] = argList
132
134
  }
133
135
  }
136
+
137
+ if (Object.keys(resolverVars).length) {
138
+ resolverInfo = { [info.fieldName]: resolverVars }
139
+ }
140
+
141
+ return resolverInfo
134
142
  }
135
143
 
136
144
  module.exports = GraphQLResolvePlugin
@@ -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,21 @@ 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
16
17
  } = require('../../dd-trace/src/plugins/util/test')
17
18
  const { COMPONENT } = require('../../dd-trace/src/constants')
18
19
  const id = require('../../dd-trace/src/id')
20
+ const {
21
+ TELEMETRY_EVENT_CREATED,
22
+ TELEMETRY_EVENT_FINISHED,
23
+ TELEMETRY_CODE_COVERAGE_STARTED,
24
+ TELEMETRY_CODE_COVERAGE_FINISHED,
25
+ TELEMETRY_ITR_FORCED_TO_RUN,
26
+ TELEMETRY_CODE_COVERAGE_EMPTY,
27
+ TELEMETRY_ITR_UNSKIPPABLE,
28
+ TELEMETRY_CODE_COVERAGE_NUM_FILES
29
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
19
30
 
20
31
  const isJestWorker = !!process.env.JEST_WORKER_ID
21
32
 
@@ -81,7 +92,9 @@ class JestPlugin extends CiPlugin {
81
92
  )
82
93
 
83
94
  this.testModuleSpan.finish()
95
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
84
96
  this.testSessionSpan.finish()
97
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
85
98
  finishAllTraceSpans(this.testSessionSpan)
86
99
  this.tracer._exporter.flush()
87
100
  })
@@ -103,7 +116,8 @@ class JestPlugin extends CiPlugin {
103
116
  _ddTestCommand: testCommand,
104
117
  _ddTestModuleId: testModuleId,
105
118
  _ddForcedToRun,
106
- _ddUnskippable
119
+ _ddUnskippable,
120
+ _ddTestCodeCoverageEnabled
107
121
  } = testEnvironmentOptions
108
122
 
109
123
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -114,8 +128,10 @@ class JestPlugin extends CiPlugin {
114
128
  const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
115
129
 
116
130
  if (_ddUnskippable) {
131
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
117
132
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
118
133
  if (_ddForcedToRun) {
134
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
119
135
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
120
136
  }
121
137
  }
@@ -128,6 +144,10 @@ class JestPlugin extends CiPlugin {
128
144
  ...testSuiteMetadata
129
145
  }
130
146
  })
147
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
148
+ if (_ddTestCodeCoverageEnabled) {
149
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
150
+ }
131
151
  })
132
152
 
133
153
  this.addSub('ci:jest:worker-report:trace', traces => {
@@ -164,6 +184,7 @@ class JestPlugin extends CiPlugin {
164
184
  this.testSuiteSpan.setTag('error', new Error(errorMessage))
165
185
  }
166
186
  this.testSuiteSpan.finish()
187
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
167
188
  // Suites potentially run in a different process than the session,
168
189
  // so calling finishAllTraceSpans on the session span is not enough
169
190
  finishAllTraceSpans(this.testSuiteSpan)
@@ -180,14 +201,22 @@ class JestPlugin extends CiPlugin {
180
201
  * because this subscription happens in a different process from the one
181
202
  * fetching the ITR config.
182
203
  */
183
- this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
204
+ this.addSub('ci:jest:test-suite:code-coverage', ({ coverageFiles, testSuite }) => {
205
+ if (!coverageFiles.length) {
206
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
207
+ }
208
+ const files = [...coverageFiles, testSuite]
209
+
184
210
  const { _traceId, _spanId } = this.testSuiteSpan.context()
185
211
  const formattedCoverage = {
186
212
  sessionId: _traceId,
187
213
  suiteId: _spanId,
188
- files: coverageFiles
214
+ files
189
215
  }
216
+
190
217
  this.tracer._exporter.exportCoverage(formattedCoverage)
218
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
219
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, files.length)
191
220
  })
192
221
 
193
222
  this.addSub('ci:jest:test:start', (test) => {
@@ -204,6 +233,11 @@ class JestPlugin extends CiPlugin {
204
233
  span.setTag(TEST_SOURCE_START, testStartLine)
205
234
  }
206
235
  span.finish()
236
+ this.telemetry.ciVisEvent(
237
+ TELEMETRY_EVENT_FINISHED,
238
+ 'test',
239
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
240
+ )
207
241
  finishAllTraceSpans(span)
208
242
  })
209
243
 
@@ -1,19 +1,66 @@
1
1
  'use strict'
2
2
 
3
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
3
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
5
 
5
6
  class KafkajsConsumerPlugin extends ConsumerPlugin {
6
7
  static get id () { return 'kafkajs' }
7
8
  static get operation () { return 'consume' }
8
9
 
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'])
10
+ constructor () {
11
+ super(...arguments)
12
+ this.addSub('apm:kafkajs:consume:commit', message => this.commit(message))
13
+ }
14
+
15
+ /**
16
+ * Transform individual commit details sent by kafkajs' event reporter
17
+ * into actionable backlog items for DSM
18
+ *
19
+ * @typedef {object} ConsumerBacklog
20
+ * @property {number} type
21
+ * @property {string} consumer_group
22
+ * @property {string} topic
23
+ * @property {number} partition
24
+ * @property {number} offset
25
+ *
26
+ * @typedef {object} CommitEventItem
27
+ * @property {string} groupId
28
+ * @property {string} topic
29
+ * @property {number} partition
30
+ * @property {import('kafkajs/utils/long').Long} offset
31
+ *
32
+ * @param {CommitEventItem} commit
33
+ * @returns {ConsumerBacklog}
34
+ */
35
+ transformCommit (commit) {
36
+ const { groupId, partition, offset, topic } = commit
37
+ return {
38
+ partition,
39
+ topic,
40
+ type: 'kafka_commit',
41
+ offset: Number(offset),
42
+ consumer_group: groupId
43
+ }
44
+ }
45
+
46
+ commit (commitList) {
47
+ if (!this.config.dsmEnabled) return
48
+ const keys = [
49
+ 'consumer_group',
50
+ 'type',
51
+ 'partition',
52
+ 'offset',
53
+ 'topic'
54
+ ]
55
+ for (const commit of commitList.map(this.transformCommit)) {
56
+ if (keys.some(key => !commit.hasOwnProperty(key))) continue
57
+ this.tracer.setOffset(commit)
14
58
  }
59
+ }
60
+
61
+ start ({ topic, partition, message, groupId }) {
15
62
  const childOf = extract(this.tracer, message.headers)
16
- this.startSpan({
63
+ const span = this.startSpan({
17
64
  childOf,
18
65
  resource: topic,
19
66
  type: 'worker',
@@ -26,6 +73,12 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
26
73
  'kafka.partition': partition
27
74
  }
28
75
  })
76
+ if (this.config.dsmEnabled) {
77
+ const payloadSize = getMessageSize(message)
78
+ this.tracer.decodeDataStreamsContext(message.headers[CONTEXT_PROPAGATION_KEY])
79
+ this.tracer
80
+ .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], span, payloadSize)
81
+ }
29
82
  }
30
83
  }
31
84
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
4
  const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
5
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = 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,63 @@ class KafkajsProducerPlugin extends ProducerPlugin {
9
11
  static get operation () { return 'produce' }
10
12
  static get peerServicePrecursors () { return [BOOTSTRAP_SERVERS_KEY] }
11
13
 
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)
66
+ }
67
+ }
68
+
12
69
  start ({ topic, messages, bootstrapServers }) {
13
70
  let pathwayCtx
14
- if (this.config.dsmEnabled) {
15
- const dataStreamsContext = this.tracer
16
- .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'])
17
- pathwayCtx = encodePathwayContext(dataStreamsContext)
18
- }
19
71
  const span = this.startSpan({
20
72
  resource: topic,
21
73
  meta: {
@@ -31,8 +83,14 @@ class KafkajsProducerPlugin extends ProducerPlugin {
31
83
  }
32
84
  for (const message of messages) {
33
85
  if (typeof message === 'object') {
34
- if (this.config.dsmEnabled) message.headers['dd-pathway-ctx'] = pathwayCtx
35
86
  this.tracer.inject(span, 'text_map', message.headers)
87
+ if (this.config.dsmEnabled) {
88
+ const payloadSize = getMessageSize(message)
89
+ const dataStreamsContext = this.tracer
90
+ .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'], span, payloadSize)
91
+ pathwayCtx = encodePathwayContext(dataStreamsContext)
92
+ message.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
93
+ }
36
94
  }
37
95
  }
38
96
  }
@@ -13,9 +13,20 @@ const {
13
13
  addIntelligentTestRunnerSpanTags,
14
14
  TEST_SOURCE_START,
15
15
  TEST_ITR_UNSKIPPABLE,
16
- TEST_ITR_FORCED_RUN
16
+ TEST_ITR_FORCED_RUN,
17
+ TEST_CODE_OWNERS
17
18
  } = require('../../dd-trace/src/plugins/util/test')
18
19
  const { COMPONENT } = require('../../dd-trace/src/constants')
20
+ const {
21
+ TELEMETRY_EVENT_CREATED,
22
+ TELEMETRY_EVENT_FINISHED,
23
+ TELEMETRY_CODE_COVERAGE_STARTED,
24
+ TELEMETRY_CODE_COVERAGE_FINISHED,
25
+ TELEMETRY_ITR_FORCED_TO_RUN,
26
+ TELEMETRY_CODE_COVERAGE_EMPTY,
27
+ TELEMETRY_ITR_UNSKIPPABLE,
28
+ TELEMETRY_CODE_COVERAGE_NUM_FILES
29
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
19
30
 
20
31
  class MochaPlugin extends CiPlugin {
21
32
  static get id () {
@@ -35,6 +46,10 @@ class MochaPlugin extends CiPlugin {
35
46
  }
36
47
  const testSuiteSpan = this._testSuites.get(suiteFile)
37
48
 
49
+ if (!coverageFiles.length) {
50
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
51
+ }
52
+
38
53
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
39
54
  .map(filename => getTestSuitePath(filename, this.sourceRoot))
40
55
 
@@ -47,6 +62,8 @@ class MochaPlugin extends CiPlugin {
47
62
  }
48
63
 
49
64
  this.tracer._exporter.exportCoverage(formattedCoverage)
65
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
66
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
50
67
  })
51
68
 
52
69
  this.addSub('ci:mocha:test-suite:start', ({ testSuite, isUnskippable, isForcedToRun }) => {
@@ -59,9 +76,11 @@ class MochaPlugin extends CiPlugin {
59
76
  )
60
77
  if (isUnskippable) {
61
78
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
79
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
62
80
  }
63
81
  if (isForcedToRun) {
64
82
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
83
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
65
84
  }
66
85
 
67
86
  const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
@@ -72,6 +91,10 @@ class MochaPlugin extends CiPlugin {
72
91
  ...testSuiteMetadata
73
92
  }
74
93
  })
94
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
95
+ if (this.itrConfig?.isCodeCoverageEnabled) {
96
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
97
+ }
75
98
  this.enter(testSuiteSpan, store)
76
99
  this._testSuites.set(testSuite, testSuiteSpan)
77
100
  })
@@ -85,6 +108,7 @@ class MochaPlugin extends CiPlugin {
85
108
  span.setTag(TEST_STATUS, status)
86
109
  }
87
110
  span.finish()
111
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
88
112
  }
89
113
  })
90
114
 
@@ -113,6 +137,11 @@ class MochaPlugin extends CiPlugin {
113
137
  span.setTag(TEST_STATUS, status)
114
138
 
115
139
  span.finish()
140
+ this.telemetry.ciVisEvent(
141
+ TELEMETRY_EVENT_FINISHED,
142
+ 'test',
143
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
144
+ )
116
145
  finishAllTraceSpans(span)
117
146
  }
118
147
  })
@@ -179,7 +208,9 @@ class MochaPlugin extends CiPlugin {
179
208
  )
180
209
 
181
210
  this.testModuleSpan.finish()
211
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
182
212
  this.testSessionSpan.finish()
213
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
183
214
  finishAllTraceSpans(this.testSessionSpan)
184
215
  }
185
216
  this.itrConfig = null