dd-trace 5.85.0 → 5.86.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 (39) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +1 -1
  3. package/packages/datadog-core/src/storage.js +30 -12
  4. package/packages/datadog-instrumentations/src/jest.js +34 -9
  5. package/packages/datadog-instrumentations/src/mocha/main.js +9 -0
  6. package/packages/datadog-instrumentations/src/prisma.js +225 -30
  7. package/packages/datadog-instrumentations/src/ws.js +22 -0
  8. package/packages/datadog-plugin-cucumber/src/index.js +4 -10
  9. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -1
  10. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -4
  11. package/packages/datadog-plugin-http/src/server.js +23 -8
  12. package/packages/datadog-plugin-jest/src/index.js +29 -10
  13. package/packages/datadog-plugin-jest/src/util.js +7 -1
  14. package/packages/datadog-plugin-mocha/src/index.js +5 -17
  15. package/packages/datadog-plugin-playwright/src/index.js +3 -0
  16. package/packages/datadog-plugin-prisma/src/datadog-tracing-helper.js +37 -14
  17. package/packages/datadog-plugin-prisma/src/index.js +8 -5
  18. package/packages/datadog-plugin-router/src/index.js +28 -19
  19. package/packages/datadog-plugin-vitest/src/index.js +6 -10
  20. package/packages/datadog-plugin-ws/src/server.js +8 -0
  21. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
  22. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  23. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +19 -0
  24. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +15 -0
  25. package/packages/dd-trace/src/ci-visibility/telemetry.js +36 -0
  26. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +44 -1
  27. package/packages/dd-trace/src/exporters/common/request.js +35 -35
  28. package/packages/dd-trace/src/id.js +1 -1
  29. package/packages/dd-trace/src/lambda/context.js +27 -0
  30. package/packages/dd-trace/src/lambda/handler.js +5 -18
  31. package/packages/dd-trace/src/log/writer.js +1 -5
  32. package/packages/dd-trace/src/plugins/ci_plugin.js +63 -1
  33. package/packages/dd-trace/src/plugins/util/git.js +27 -30
  34. package/packages/dd-trace/src/plugins/util/test.js +3 -1
  35. package/packages/dd-trace/src/plugins/util/web.js +1 -0
  36. package/packages/dd-trace/src/profiling/config.js +6 -14
  37. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -24
  38. package/packages/dd-trace/src/profiling/profiler.js +2 -0
  39. package/packages/dd-trace/src/startup-log.js +1 -0
@@ -13,12 +13,11 @@ class HttpServerPlugin extends ServerPlugin {
13
13
 
14
14
  constructor (...args) {
15
15
  super(...args)
16
- this._parentStore = undefined
17
16
  this.addTraceSub('exit', message => this.exit(message))
18
17
  }
19
18
 
20
19
  start ({ req, res, abortController }) {
21
- const store = storage('legacy').getStore()
20
+ let store = storage('legacy').getStore()
22
21
  const span = web.startSpan(
23
22
  this.tracer,
24
23
  {
@@ -32,11 +31,21 @@ class HttpServerPlugin extends ServerPlugin {
32
31
  span.setTag(COMPONENT, this.constructor.id)
33
32
  span._integrationName = this.constructor.id
34
33
 
35
- this._parentStore = store
36
- this.enter(span, { ...store, req, res })
37
-
38
34
  const context = web.getContext(req)
39
35
 
36
+ if (context) {
37
+ context.parentStore = store
38
+ }
39
+
40
+ // Only AppSec needs the request scope to be active for any async work that
41
+ // may be scheduled after the synchronous `request` event returns (e.g.
42
+ // Fastify).
43
+ if (incomingHttpRequestStart.hasSubscribers) {
44
+ store = { ...store, req, res }
45
+ }
46
+
47
+ this.enter(span, store)
48
+
40
49
  if (!context.instrumented) {
41
50
  context.res.writeHead = web.wrapWriteHead(context)
42
51
  context.instrumented = true
@@ -64,9 +73,15 @@ class HttpServerPlugin extends ServerPlugin {
64
73
  }
65
74
 
66
75
  exit ({ req }) {
67
- const span = this._parentStore && this._parentStore.span
68
- this.enter(span, this._parentStore)
69
- this._parentStore = undefined
76
+ const context = web.getContext(req)
77
+ const parentStore = context?.parentStore
78
+
79
+ const span = parentStore?.span
80
+ this.enter(span, parentStore)
81
+
82
+ if (context) {
83
+ context.parentStore = undefined
84
+ }
70
85
  }
71
86
 
72
87
  configure (config) {
@@ -3,6 +3,7 @@
3
3
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
6
+ const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
6
7
 
7
8
  const {
8
9
  TEST_STATUS,
@@ -25,8 +26,6 @@ const {
25
26
  TEST_EARLY_FLAKE_ENABLED,
26
27
  TEST_EARLY_FLAKE_ABORT_REASON,
27
28
  JEST_DISPLAY_NAME,
28
- TEST_IS_RUM_ACTIVE,
29
- TEST_BROWSER_DRIVER,
30
29
  getFormattedError,
31
30
  TEST_RETRY_REASON,
32
31
  TEST_MANAGEMENT_ENABLED,
@@ -157,7 +156,9 @@ class JestPlugin extends CiPlugin {
157
156
  this.testModuleSpan.finish()
158
157
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
159
158
  this.testSessionSpan.finish()
160
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
159
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
160
+ hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
161
+ })
161
162
  finishAllTraceSpans(this.testSessionSpan)
162
163
 
163
164
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
@@ -165,6 +166,7 @@ class JestPlugin extends CiPlugin {
165
166
  autoInjected: !!getValueFromEnvSources('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER'),
166
167
  })
167
168
 
169
+ appClosingTelemetry()
168
170
  this.tracer._exporter.flush(() => {
169
171
  if (onDone) {
170
172
  onDone()
@@ -277,6 +279,23 @@ class JestPlugin extends CiPlugin {
277
279
  }
278
280
  })
279
281
 
282
+ this.addSub('ci:jest:worker-report:telemetry', data => {
283
+ const telemetryEvents = JSON.parse(data)
284
+ for (const event of telemetryEvents) {
285
+ if (event.type === 'ciVisEvent') {
286
+ this.telemetry.ciVisEvent(event.name, event.testLevel, {
287
+ ...event.tags,
288
+ testFramework: event.testFramework,
289
+ isUnsupportedCIProvider: event.isUnsupportedCIProvider,
290
+ })
291
+ } else if (event.type === 'count') {
292
+ this.telemetry.count(event.name, event.tags, event.value)
293
+ } else if (event.type === 'distribution') {
294
+ this.telemetry.distribution(event.name, event.tags, event.measure)
295
+ }
296
+ }
297
+ })
298
+
280
299
  this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error, testSuiteAbsolutePath }) => {
281
300
  const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
282
301
  if (!testSuiteSpan) {
@@ -391,16 +410,10 @@ class JestPlugin extends CiPlugin {
391
410
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
392
411
  }
393
412
 
394
- const spanTags = span.context()._tags
395
413
  this.telemetry.ciVisEvent(
396
414
  TELEMETRY_EVENT_FINISHED,
397
415
  'test',
398
- {
399
- hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
400
- isNew: spanTags[TEST_IS_NEW] === 'true',
401
- isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
402
- browserDriver: spanTags[TEST_BROWSER_DRIVER],
403
- }
416
+ this.getTestTelemetryTags(span)
404
417
  )
405
418
 
406
419
  span.finish()
@@ -433,6 +446,12 @@ class JestPlugin extends CiPlugin {
433
446
  span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
434
447
  }
435
448
 
449
+ this.telemetry.ciVisEvent(
450
+ TELEMETRY_EVENT_FINISHED,
451
+ 'test',
452
+ this.getTestTelemetryTags(span)
453
+ )
454
+
436
455
  span.finish()
437
456
  })
438
457
 
@@ -166,4 +166,10 @@ function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
166
166
  }
167
167
  }
168
168
 
169
- module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun, isMarkedAsUnskippable }
169
+ module.exports = {
170
+ SEED_SUFFIX_RE,
171
+ getFormattedJestTestParameters,
172
+ getJestTestName,
173
+ getJestSuitesToRun,
174
+ isMarkedAsUnskippable,
175
+ }
@@ -23,8 +23,6 @@ const {
23
23
  TEST_EARLY_FLAKE_ENABLED,
24
24
  TEST_EARLY_FLAKE_ABORT_REASON,
25
25
  MOCHA_IS_PARALLEL,
26
- TEST_IS_RUM_ACTIVE,
27
- TEST_BROWSER_DRIVER,
28
26
  TEST_RETRY_REASON,
29
27
  TEST_MANAGEMENT_ENABLED,
30
28
  TEST_MANAGEMENT_IS_QUARANTINED,
@@ -236,16 +234,10 @@ class MochaPlugin extends CiPlugin {
236
234
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
237
235
  }
238
236
 
239
- const spanTags = span.context()._tags
240
237
  this.telemetry.ciVisEvent(
241
238
  TELEMETRY_EVENT_FINISHED,
242
239
  'test',
243
- {
244
- hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
245
- isNew: spanTags[TEST_IS_NEW] === 'true',
246
- isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
247
- browserDriver: spanTags[TEST_BROWSER_DRIVER],
248
- }
240
+ this.getTestTelemetryTags(span)
249
241
  )
250
242
 
251
243
  span.finish()
@@ -310,16 +302,10 @@ class MochaPlugin extends CiPlugin {
310
302
  span.setTag('error', err)
311
303
  }
312
304
 
313
- const spanTags = span.context()._tags
314
305
  this.telemetry.ciVisEvent(
315
306
  TELEMETRY_EVENT_FINISHED,
316
307
  'test',
317
- {
318
- hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
319
- isNew: spanTags[TEST_IS_NEW] === 'true',
320
- isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
321
- browserDriver: spanTags[TEST_BROWSER_DRIVER],
322
- }
308
+ this.getTestTelemetryTags(span)
323
309
  )
324
310
  if (isFirstAttempt && willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
325
311
  const probeInformation = this.addDiProbe(err)
@@ -402,7 +388,9 @@ class MochaPlugin extends CiPlugin {
402
388
  this.testModuleSpan.finish()
403
389
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
404
390
  this.testSessionSpan.finish()
405
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
391
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
392
+ hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
393
+ })
406
394
  finishAllTraceSpans(this.testSessionSpan)
407
395
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
408
396
  provider: this.ciProviderName,
@@ -388,6 +388,9 @@ class PlaywrightPlugin extends CiPlugin {
388
388
  isNew,
389
389
  isRum: isRUMActive,
390
390
  browserDriver: 'playwright',
391
+ isQuarantined,
392
+ isDisabled,
393
+ isModified,
391
394
  }
392
395
  )
393
396
  span.finish()
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const tracingChannel = require('dc-polyfill').tracingChannel
3
+ const { tracingChannel } = /** @type {import('node:diagnostics_channel')} */ (require('dc-polyfill'))
4
4
  const clientCH = tracingChannel('apm:prisma')
5
5
  const { storage } = require('../../datadog-core')
6
6
 
@@ -10,21 +10,12 @@ const allowedClientSpanOperations = new Set([
10
10
  'transaction',
11
11
  ])
12
12
 
13
- /**
14
- * @typedef {object} DbConfig
15
- * @property {string} [user]
16
- * @property {string} [password]
17
- * @property {string} [host]
18
- * @property {string} [port]
19
- * @property {string} [database]
20
- */
21
-
22
13
  class DatadogTracingHelper {
23
14
  #prismaClient
24
15
  #dbConfig
25
16
 
26
17
  /**
27
- * @param {DbConfig|undefined} dbConfig
18
+ * @param {import('../../datadog-instrumentations/src/prisma').DbConfig|undefined} dbConfig
28
19
  * @param {import('./index')} prismaClient
29
20
  */
30
21
  constructor (dbConfig, prismaClient) {
@@ -36,7 +27,12 @@ class DatadogTracingHelper {
36
27
  return true
37
28
  }
38
29
 
39
- // needs a sampled tracecontext to generate engine spans
30
+ /**
31
+ * Needs a sampled tracecontext to generate engine spans
32
+ *
33
+ * @param {object} [context]
34
+ * @returns {string}
35
+ */
40
36
  getTraceParent (context) {
41
37
  const store = storage('legacy').getStore()
42
38
  const span = store?.span
@@ -57,12 +53,31 @@ class DatadogTracingHelper {
57
53
  return '00-00000000000000000000000000000000-0000000000000000-01'
58
54
  }
59
55
 
56
+ /**
57
+ * @param {object[]} spans
58
+ */
60
59
  dispatchEngineSpans (spans) {
60
+ if (!spans?.length) {
61
+ return
62
+ }
63
+ const childrenByParent = new Map()
61
64
  for (const span of spans) {
62
- if (span.parentId === null) {
63
- this.#prismaClient.startEngineSpan({ engineSpan: span, allEngineSpans: spans, dbConfig: this.#dbConfig })
65
+ const parentId = span.parentId
66
+ const children = childrenByParent.get(parentId)
67
+ if (children) {
68
+ children.push(span)
69
+ } else {
70
+ childrenByParent.set(parentId, [span])
64
71
  }
65
72
  }
73
+
74
+ const roots = childrenByParent.get(null)
75
+ if (!roots) {
76
+ return
77
+ }
78
+ for (const span of roots) {
79
+ this.#prismaClient.startEngineSpan({ engineSpan: span, childrenByParent, dbConfig: this.#dbConfig })
80
+ }
66
81
  }
67
82
 
68
83
  getActiveContext () {
@@ -70,7 +85,15 @@ class DatadogTracingHelper {
70
85
  return store?.span?._spanContext
71
86
  }
72
87
 
88
+ /**
89
+ * @param {object} options
90
+ * @param {Function} callback
91
+ * @returns {unknown}
92
+ */
73
93
  runInChildSpan (options, callback) {
94
+ if (!clientCH.start?.hasSubscribers) {
95
+ return callback.apply(this, arguments)
96
+ }
74
97
  if (typeof options === 'string') {
75
98
  options = {
76
99
  name: options,
@@ -32,13 +32,15 @@ class PrismaPlugin extends DatabasePlugin {
32
32
  super(...args)
33
33
 
34
34
  // Subscribe to helper initialization to inject callbacks
35
- this.addSub('apm:prisma:helper:init', (prismaHelperCtx) => {
35
+ this.addSub('apm:prisma:helper:init', (ctx) => {
36
+ const prismaHelperCtx =
37
+ /** @type {import('../../datadog-instrumentations/src/prisma').PrismaHelperCtx} */ (ctx)
36
38
  prismaHelperCtx.helper = new DatadogTracingHelper(prismaHelperCtx.dbConfig, this)
37
39
  })
38
40
  }
39
41
 
40
42
  startEngineSpan (ctx) {
41
- const { engineSpan, allEngineSpans, childOf, dbConfig } = ctx
43
+ const { engineSpan, childrenByParent, childOf, dbConfig } = ctx
42
44
  const service = this.serviceName({ pluginConfig: this.config, system: this.system })
43
45
  const spanName = engineSpan.name.slice(14) // remove 'prisma:engine:' prefix
44
46
  const options = {
@@ -71,9 +73,10 @@ class PrismaPlugin extends DatabasePlugin {
71
73
 
72
74
  const activeSpan = this.startSpan(this.operationName({ operation: 'engine' }), options)
73
75
  activeSpan._startTime = hrTimeToUnixTimeMs(engineSpan.startTime)
74
- for (const span of allEngineSpans) {
75
- if (span.parentId === engineSpan.id) {
76
- const startCtx = { engineSpan: span, allEngineSpans, childOf: activeSpan, dbConfig }
76
+ const children = childrenByParent.get(engineSpan.id)
77
+ if (children) {
78
+ for (const span of children) {
79
+ const startCtx = { engineSpan: span, childrenByParent, childOf: activeSpan, dbConfig }
77
80
  this.startEngineSpan(startCtx)
78
81
  }
79
82
  }
@@ -9,26 +9,31 @@ const { COMPONENT } = require('../../dd-trace/src/constants')
9
9
  class RouterPlugin extends WebPlugin {
10
10
  static id = 'router'
11
11
 
12
+ #storeStacks = new WeakMap()
13
+ #contexts = new WeakMap()
14
+
12
15
  constructor (...args) {
13
16
  super(...args)
14
17
 
15
- this._storeStack = []
16
- this._contexts = new WeakMap()
17
-
18
18
  this.addSub(`apm:${this.constructor.id}:middleware:enter`, ({ req, name, route }) => {
19
- const childOf = this._getActive(req) || this._getStoreSpan()
19
+ const childOf = this.#getActive(req) || this.#getStoreSpan()
20
20
 
21
21
  if (!childOf) return
22
22
 
23
- const span = this._getMiddlewareSpan(name, childOf)
24
- const context = this._createContext(req, route, childOf)
23
+ const span = this.#getMiddlewareSpan(name, childOf)
24
+ const context = this.#createContext(req, route, childOf)
25
25
 
26
26
  if (childOf !== span) {
27
27
  context.middleware.push(span)
28
28
  }
29
29
 
30
30
  const store = storage('legacy').getStore()
31
- this._storeStack.push(store)
31
+ let storeStack = this.#storeStacks.get(req)
32
+ if (!storeStack) {
33
+ storeStack = []
34
+ this.#storeStacks.set(req, storeStack)
35
+ }
36
+ storeStack.push(store)
32
37
  this.enter(span, store)
33
38
 
34
39
  web.patch(req)
@@ -36,7 +41,7 @@ class RouterPlugin extends WebPlugin {
36
41
  })
37
42
 
38
43
  this.addSub(`apm:${this.constructor.id}:middleware:next`, ({ req }) => {
39
- const context = this._contexts.get(req)
44
+ const context = this.#contexts.get(req)
40
45
 
41
46
  if (!context) return
42
47
 
@@ -44,7 +49,7 @@ class RouterPlugin extends WebPlugin {
44
49
  })
45
50
 
46
51
  this.addSub(`apm:${this.constructor.id}:middleware:finish`, ({ req }) => {
47
- const context = this._contexts.get(req)
52
+ const context = this.#contexts.get(req)
48
53
 
49
54
  if (!context || context.middleware.length === 0) return
50
55
 
@@ -52,7 +57,11 @@ class RouterPlugin extends WebPlugin {
52
57
  })
53
58
 
54
59
  this.addSub(`apm:${this.constructor.id}:middleware:exit`, ({ req }) => {
55
- const savedStore = this._storeStack.pop()
60
+ const storeStack = this.#storeStacks.get(req)
61
+ const savedStore = storeStack && storeStack.pop()
62
+ if (storeStack && storeStack.length === 0) {
63
+ this.#storeStacks.delete(req)
64
+ }
56
65
  const span = savedStore && savedStore.span
57
66
  this.enter(span, savedStore)
58
67
  })
@@ -62,7 +71,7 @@ class RouterPlugin extends WebPlugin {
62
71
 
63
72
  if (!this.config.middleware) return
64
73
 
65
- const span = this._getActive(req)
74
+ const span = this.#getActive(req)
66
75
 
67
76
  if (!span) return
68
77
 
@@ -70,7 +79,7 @@ class RouterPlugin extends WebPlugin {
70
79
  })
71
80
 
72
81
  this.addSub('apm:http:server:request:finish', ({ req }) => {
73
- const context = this._contexts.get(req)
82
+ const context = this.#contexts.get(req)
74
83
 
75
84
  if (!context) return
76
85
 
@@ -82,8 +91,8 @@ class RouterPlugin extends WebPlugin {
82
91
  })
83
92
  }
84
93
 
85
- _getActive (req) {
86
- const context = this._contexts.get(req)
94
+ #getActive (req) {
95
+ const context = this.#contexts.get(req)
87
96
 
88
97
  if (!context) return
89
98
  if (context.middleware.length === 0) return context.span
@@ -91,13 +100,13 @@ class RouterPlugin extends WebPlugin {
91
100
  return context.middleware.at(-1)
92
101
  }
93
102
 
94
- _getStoreSpan () {
103
+ #getStoreSpan () {
95
104
  const store = storage('legacy').getStore()
96
105
 
97
106
  return store && store.span
98
107
  }
99
108
 
100
- _getMiddlewareSpan (name, childOf) {
109
+ #getMiddlewareSpan (name, childOf) {
101
110
  if (this.config.middleware === false) {
102
111
  return childOf
103
112
  }
@@ -116,8 +125,8 @@ class RouterPlugin extends WebPlugin {
116
125
  return span
117
126
  }
118
127
 
119
- _createContext (req, route, span) {
120
- let context = this._contexts.get(req)
128
+ #createContext (req, route, span) {
129
+ let context = this.#contexts.get(req)
121
130
 
122
131
  if (!route || route === '/' || route === '*') {
123
132
  route = ''
@@ -140,7 +149,7 @@ class RouterPlugin extends WebPlugin {
140
149
  middleware: [],
141
150
  }
142
151
 
143
- this._contexts.set(req, context)
152
+ this.#contexts.set(req, context)
144
153
  }
145
154
 
146
155
  return context
@@ -206,9 +206,7 @@ class VitestPlugin extends CiPlugin {
206
206
 
207
207
  this.addSub('ci:vitest:test:pass', ({ span, task }) => {
208
208
  if (span) {
209
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
210
- hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS],
211
- })
209
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
212
210
  span.setTag(TEST_STATUS, 'pass')
213
211
  span.finish(this.taskToFinishTime.get(task))
214
212
  finishAllTraceSpans(span)
@@ -236,9 +234,7 @@ class VitestPlugin extends CiPlugin {
236
234
  promises.setProbePromise = setProbePromise
237
235
  }
238
236
  }
239
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
240
- hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS],
241
- })
237
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
242
238
  span.setTag(TEST_STATUS, 'fail')
243
239
 
244
240
  if (error) {
@@ -272,9 +268,7 @@ class VitestPlugin extends CiPlugin {
272
268
  ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
273
269
  }
274
270
  )
275
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
276
- hasCodeowners: !!testSpan.context()._tags[TEST_CODE_OWNERS],
277
- })
271
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
278
272
  testSpan.finish()
279
273
  })
280
274
 
@@ -401,7 +395,9 @@ class VitestPlugin extends CiPlugin {
401
395
  this.testModuleSpan.finish()
402
396
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
403
397
  this.testSessionSpan.finish()
404
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
398
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
399
+ hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
400
+ })
405
401
  finishAllTraceSpans(this.testSessionSpan)
406
402
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
407
403
  provider: this.ciProviderName,
@@ -17,6 +17,14 @@ class WSServerPlugin extends TracingPlugin {
17
17
  static get type () { return 'websocket' }
18
18
  static get kind () { return 'request' }
19
19
 
20
+ constructor (...args) {
21
+ super(...args)
22
+
23
+ // Bind the setSocket channel so internal ws event handlers (data, close)
24
+ // don't capture their async context.
25
+ this.addBind('tracing:ws:server:connect:setSocket', () => {})
26
+ }
27
+
20
28
  bindStart (ctx) {
21
29
  const req = ctx.req
22
30
 
@@ -22,6 +22,7 @@ const EXCLUDED_PATH_PREFIXES = [
22
22
  'child_process',
23
23
  'node:async_hooks',
24
24
  'async_hooks',
25
+ 'node:internal/async_local_storage',
25
26
  ]
26
27
 
27
28
  function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
@@ -95,7 +95,7 @@ function getKnownTests ({
95
95
 
96
96
  const numTests = getNumFromKnownTests(knownTests)
97
97
 
98
- incrementCountMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, numTests)
98
+ distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, numTests)
99
99
  distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, {}, res.length)
100
100
 
101
101
  log.debug('Number of received known tests:', numTests)
@@ -3,6 +3,7 @@
3
3
  const {
4
4
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
5
5
  JEST_WORKER_TRACE_PAYLOAD_CODE,
6
+ JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
6
7
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
7
8
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
8
9
  JEST_WORKER_LOGS_PAYLOAD_CODE,
@@ -56,6 +57,13 @@ function getInterprocessLogsCode () {
56
57
  return null
57
58
  }
58
59
 
60
+ function getInterprocessTelemetryCode () {
61
+ if (getEnvironmentVariable('JEST_WORKER_ID')) {
62
+ return JEST_WORKER_TELEMETRY_PAYLOAD_CODE
63
+ }
64
+ return null
65
+ }
66
+
59
67
  /**
60
68
  * Lightweight exporter whose writers only do simple JSON serialization
61
69
  * of trace, coverage and logs payloads, which they send to the test framework's main process.
@@ -66,10 +74,18 @@ class TestWorkerCiVisibilityExporter {
66
74
  const interprocessTraceCode = getInterprocessTraceCode()
67
75
  const interprocessCoverageCode = getInterprocessCoverageCode()
68
76
  const interprocessLogsCode = getInterprocessLogsCode()
77
+ const interprocessTelemetryCode = getInterprocessTelemetryCode()
69
78
 
70
79
  this._writer = new Writer(interprocessTraceCode)
71
80
  this._coverageWriter = new Writer(interprocessCoverageCode)
72
81
  this._logsWriter = new Writer(interprocessLogsCode)
82
+ // TODO: add support for test workers other than Jest
83
+ if (interprocessTelemetryCode) {
84
+ this._telemetryWriter = new Writer(interprocessTelemetryCode)
85
+ this.exportTelemetry = function (telemetryEvent) {
86
+ this._telemetryWriter.append(telemetryEvent)
87
+ }
88
+ }
73
89
  }
74
90
 
75
91
  export (payload) {
@@ -89,6 +105,9 @@ class TestWorkerCiVisibilityExporter {
89
105
  this._writer.flush(onDone)
90
106
  this._coverageWriter.flush()
91
107
  this._logsWriter.flush()
108
+ if (this._telemetryWriter) {
109
+ this._telemetryWriter.flush()
110
+ }
92
111
  }
93
112
  }
94
113
 
@@ -7,6 +7,14 @@ const FormData = require('../../exporters/common/form-data')
7
7
  const request = require('../../exporters/common/request')
8
8
  const log = require('../../log')
9
9
  const { getValueFromEnvSources } = require('../../config/helper')
10
+ const {
11
+ incrementCountMetric,
12
+ distributionMetric,
13
+ TELEMETRY_COVERAGE_UPLOAD,
14
+ TELEMETRY_COVERAGE_UPLOAD_MS,
15
+ TELEMETRY_COVERAGE_UPLOAD_ERRORS,
16
+ TELEMETRY_COVERAGE_UPLOAD_BYTES,
17
+ } = require('../telemetry')
10
18
 
11
19
  const UPLOAD_TIMEOUT_MS = 30_000
12
20
 
@@ -79,8 +87,15 @@ function uploadCoverageReport (
79
87
 
80
88
  log.debug('Uploading coverage report %s to %s%s', filePath, url, options.path)
81
89
 
90
+ incrementCountMetric(TELEMETRY_COVERAGE_UPLOAD)
91
+ distributionMetric(TELEMETRY_COVERAGE_UPLOAD_BYTES, {}, compressedCoverage.length)
92
+
93
+ const startTime = Date.now()
94
+
82
95
  request(form, options, (err, res, statusCode) => {
96
+ distributionMetric(TELEMETRY_COVERAGE_UPLOAD_MS, {}, Date.now() - startTime)
83
97
  if (err) {
98
+ incrementCountMetric(TELEMETRY_COVERAGE_UPLOAD_ERRORS, { statusCode })
84
99
  log.error('Error uploading coverage report: %s', err.message)
85
100
  return callback(err)
86
101
  }