dd-trace 3.42.0 → 3.44.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 (68) hide show
  1. package/index.d.ts +5 -0
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  4. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  5. package/packages/datadog-instrumentations/src/child-process.js +4 -5
  6. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  7. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  8. package/packages/datadog-instrumentations/src/dns.js +2 -1
  9. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +9 -2
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +8 -3
  12. package/packages/datadog-instrumentations/src/helpers/register.js +18 -2
  13. package/packages/datadog-instrumentations/src/http/client.js +4 -16
  14. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  15. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  16. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  17. package/packages/datadog-instrumentations/src/jest.js +1 -1
  18. package/packages/datadog-instrumentations/src/net.js +10 -2
  19. package/packages/datadog-instrumentations/src/next.js +15 -5
  20. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  21. package/packages/datadog-plugin-cucumber/src/index.js +34 -2
  22. package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
  23. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  24. package/packages/datadog-plugin-http/src/client.js +1 -1
  25. package/packages/datadog-plugin-jest/src/index.js +38 -4
  26. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  27. package/packages/datadog-plugin-next/src/index.js +32 -6
  28. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  29. package/packages/dd-trace/src/appsec/activation.js +29 -0
  30. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  31. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  32. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  33. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  34. package/packages/dd-trace/src/appsec/channels.js +4 -1
  35. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  36. package/packages/dd-trace/src/appsec/index.js +29 -40
  37. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  38. package/packages/dd-trace/src/appsec/remote_config/index.js +40 -15
  39. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  40. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +25 -13
  41. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  42. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  43. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +36 -4
  44. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +18 -1
  45. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  46. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  47. package/packages/dd-trace/src/config.js +104 -58
  48. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  49. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  50. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  51. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  52. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  53. package/packages/dd-trace/src/plugins/ci_plugin.js +44 -8
  54. package/packages/dd-trace/src/plugins/index.js +5 -0
  55. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  56. package/packages/dd-trace/src/plugins/util/git.js +94 -19
  57. package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
  58. package/packages/dd-trace/src/priority_sampler.js +30 -38
  59. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  60. package/packages/dd-trace/src/profiling/profiler.js +7 -6
  61. package/packages/dd-trace/src/profiling/profilers/events.js +18 -13
  62. package/packages/dd-trace/src/profiling/profilers/shared.js +34 -4
  63. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  64. package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
  65. package/packages/dd-trace/src/proxy.js +4 -0
  66. package/packages/dd-trace/src/sampling_rule.js +130 -0
  67. package/packages/dd-trace/src/span_sampler.js +6 -64
  68. package/packages/dd-trace/src/telemetry/index.js +43 -5
@@ -12,10 +12,21 @@ const {
12
12
  getTestSuiteCommonTags,
13
13
  addIntelligentTestRunnerSpanTags,
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 { RESOURCE_NAME } = require('../../../ext/tags')
18
19
  const { COMPONENT, ERROR_MESSAGE } = 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 CucumberPlugin extends CiPlugin {
21
32
  static get id () {
@@ -54,7 +65,9 @@ class CucumberPlugin extends CiPlugin {
54
65
  this.testSessionSpan.setTag(TEST_STATUS, status)
55
66
  this.testModuleSpan.setTag(TEST_STATUS, status)
56
67
  this.testModuleSpan.finish()
68
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
57
69
  this.testSessionSpan.finish()
70
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
58
71
  finishAllTraceSpans(this.testSessionSpan)
59
72
 
60
73
  this.itrConfig = null
@@ -69,9 +82,11 @@ class CucumberPlugin extends CiPlugin {
69
82
  'cucumber'
70
83
  )
71
84
  if (isUnskippable) {
85
+ this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
72
86
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
73
87
  }
74
88
  if (isForcedToRun) {
89
+ this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
75
90
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
76
91
  }
77
92
  this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
@@ -82,20 +97,31 @@ class CucumberPlugin extends CiPlugin {
82
97
  ...testSuiteMetadata
83
98
  }
84
99
  })
100
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
101
+ if (this.itrConfig?.isCodeCoverageEnabled) {
102
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
103
+ }
85
104
  })
86
105
 
87
106
  this.addSub('ci:cucumber:test-suite:finish', status => {
88
107
  this.testSuiteSpan.setTag(TEST_STATUS, status)
89
108
  this.testSuiteSpan.finish()
109
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
90
110
  })
91
111
 
92
112
  this.addSub('ci:cucumber:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
93
- if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
113
+ if (!this.itrConfig?.isCodeCoverageEnabled) {
94
114
  return
95
115
  }
116
+ if (!coverageFiles.length) {
117
+ this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
118
+ }
119
+
96
120
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
97
121
  .map(filename => getTestSuitePath(filename, this.sourceRoot))
98
122
 
123
+ this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
124
+
99
125
  const formattedCoverage = {
100
126
  sessionId: this.testSuiteSpan.context()._traceId,
101
127
  suiteId: this.testSuiteSpan.context()._spanId,
@@ -103,6 +129,7 @@ class CucumberPlugin extends CiPlugin {
103
129
  }
104
130
 
105
131
  this.tracer._exporter.exportCoverage(formattedCoverage)
132
+ this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: 'istanbul' })
106
133
  })
107
134
 
108
135
  this.addSub('ci:cucumber:test:start', ({ testName, fullTestSuite, testSourceLine }) => {
@@ -142,6 +169,11 @@ class CucumberPlugin extends CiPlugin {
142
169
  }
143
170
 
144
171
  span.finish()
172
+ this.telemetry.ciVisEvent(
173
+ TELEMETRY_EVENT_FINISHED,
174
+ 'test',
175
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
176
+ )
145
177
  if (!isStep) {
146
178
  finishAllTraceSpans(span)
147
179
  }
@@ -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
@@ -121,7 +121,7 @@ class HttpClientPlugin extends ClientPlugin {
121
121
  } else {
122
122
  // conditions for no error:
123
123
  // 1. not using a custom agent instance with custom timeout specified
124
- // 2. no invocation of `req.setTimeout` or `socket.setTimeout`
124
+ // 2. no invocation of `req.setTimeout`
125
125
  if (!args.options.agent?.options.timeout && !customRequestTimeout) return
126
126
 
127
127
  span.setTag('error', 1)
@@ -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
 
@@ -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
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
+ const errorPages = ['/404', '/500', '/_error', '/_not-found']
10
+
9
11
  class NextPlugin extends ServerPlugin {
10
12
  static get id () {
11
13
  return 'next'
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
40
42
  }
41
43
 
42
44
  error ({ span, error }) {
45
+ if (!span) {
46
+ const store = storage.getStore()
47
+ if (!store) return
48
+
49
+ span = store.span
50
+ }
51
+
43
52
  this.addError(error, span)
44
53
  }
45
54
 
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
50
59
 
51
60
  const span = store.span
52
61
  const error = span.context()._tags['error']
53
-
54
- if (!this.config.validateStatus(res.statusCode) && !error) {
55
- span.setTag('error', req.error || nextRequest.error || true)
56
- web.addError(req, req.error || nextRequest.error || true)
62
+ const requestError = req.error || nextRequest.error
63
+
64
+ if (requestError) {
65
+ // prioritize user-set errors from API routes
66
+ span.setTag('error', requestError)
67
+ web.addError(req, requestError)
68
+ } else if (error) {
69
+ // general error handling
70
+ span.setTag('error', error)
71
+ web.addError(req, requestError || error)
72
+ } else if (!this.config.validateStatus(res.statusCode)) {
73
+ // where there's no error, we still need to validate status
74
+ span.setTag('error', true)
75
+ web.addError(req, true)
57
76
  }
58
77
 
59
78
  span.addTags({
@@ -73,14 +92,21 @@ class NextPlugin extends ServerPlugin {
73
92
  const span = store.span
74
93
  const req = this._requests.get(span)
75
94
 
95
+ // safeguard against missing req in complicated timeout scenarios
96
+ if (!req) return
97
+
76
98
  // Only use error page names if there's not already a name
77
99
  const current = span.context()._tags['next.page']
78
- if (current && ['/404', '/500', '/_error', '/_not-found'].includes(page)) {
100
+ const isErrorPage = errorPages.includes(page)
101
+
102
+ if (current && isErrorPage) {
79
103
  return
80
104
  }
81
105
 
82
106
  // remove ending /route or /page for appDir projects
83
- if (isAppPath) page = page.substring(0, page.lastIndexOf('/'))
107
+ // need to check if not an error page too, as those are marked as app directory
108
+ // in newer versions
109
+ if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
84
110
 
85
111
  // handle static resource
86
112
  if (isStatic) {
@@ -8,10 +8,15 @@ const {
8
8
  finishAllTraceSpans,
9
9
  getTestSuitePath,
10
10
  getTestSuiteCommonTags,
11
- TEST_SOURCE_START
11
+ TEST_SOURCE_START,
12
+ TEST_CODE_OWNERS
12
13
  } = require('../../dd-trace/src/plugins/util/test')
13
14
  const { RESOURCE_NAME } = require('../../../ext/tags')
14
15
  const { COMPONENT } = require('../../dd-trace/src/constants')
16
+ const {
17
+ TELEMETRY_EVENT_CREATED,
18
+ TELEMETRY_EVENT_FINISHED
19
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
15
20
 
16
21
  class PlaywrightPlugin extends CiPlugin {
17
22
  static get id () {
@@ -28,7 +33,9 @@ class PlaywrightPlugin extends CiPlugin {
28
33
  this.testSessionSpan.setTag(TEST_STATUS, status)
29
34
 
30
35
  this.testModuleSpan.finish()
36
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
31
37
  this.testSessionSpan.finish()
38
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
32
39
  finishAllTraceSpans(this.testSessionSpan)
33
40
  this.tracer._exporter.flush(onDone)
34
41
  })
@@ -52,6 +59,7 @@ class PlaywrightPlugin extends CiPlugin {
52
59
  ...testSuiteMetadata
53
60
  }
54
61
  })
62
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
55
63
  this.enter(testSuiteSpan, store)
56
64
 
57
65
  this._testSuites.set(testSuite, testSuiteSpan)
@@ -63,6 +71,7 @@ class PlaywrightPlugin extends CiPlugin {
63
71
  if (!span) return
64
72
  span.setTag(TEST_STATUS, status)
65
73
  span.finish()
74
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
66
75
  })
67
76
 
68
77
  this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
@@ -104,6 +113,13 @@ class PlaywrightPlugin extends CiPlugin {
104
113
  })
105
114
 
106
115
  span.finish()
116
+
117
+ this.telemetry.ciVisEvent(
118
+ TELEMETRY_EVENT_FINISHED,
119
+ 'test',
120
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
121
+ )
122
+
107
123
  finishAllTraceSpans(span)
108
124
  })
109
125
  }