dd-trace 5.87.0 → 5.88.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 (77) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/index.d.ts +225 -4
  5. package/package.json +9 -6
  6. package/packages/datadog-instrumentations/src/ai.js +54 -90
  7. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +55 -14
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +15 -13
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +21 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +138 -12
  15. package/packages/datadog-instrumentations/src/jest.js +76 -12
  16. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  17. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  18. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  19. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  20. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  21. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  22. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +26 -0
  24. package/packages/datadog-plugin-cypress/src/support.js +48 -8
  25. package/packages/datadog-plugin-jest/src/index.js +12 -2
  26. package/packages/datadog-plugin-jest/src/util.js +2 -1
  27. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  28. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  29. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  30. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  31. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  32. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  34. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  35. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  36. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  39. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  40. package/packages/dd-trace/src/azure_metadata.js +0 -2
  41. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  42. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -0
  43. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  44. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  45. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  46. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  47. package/packages/dd-trace/src/config/defaults.js +148 -197
  48. package/packages/dd-trace/src/config/helper.js +43 -1
  49. package/packages/dd-trace/src/config/index.js +36 -14
  50. package/packages/dd-trace/src/config/supported-configurations.json +4115 -512
  51. package/packages/dd-trace/src/constants.js +0 -2
  52. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  53. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  54. package/packages/dd-trace/src/datastreams/processor.js +14 -1
  55. package/packages/dd-trace/src/encode/agentless-json.js +141 -0
  56. package/packages/dd-trace/src/exporter.js +2 -0
  57. package/packages/dd-trace/src/exporters/agent/writer.js +22 -8
  58. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  59. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  60. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  61. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  62. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  63. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  64. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  65. package/packages/dd-trace/src/opentracing/span.js +6 -4
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  67. package/packages/dd-trace/src/plugins/database.js +15 -2
  68. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  69. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  70. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  71. package/packages/dd-trace/src/proxy.js +4 -0
  72. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  73. package/packages/dd-trace/src/startup-log.js +1 -1
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  75. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  76. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  77. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
@@ -10,6 +10,12 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
10
10
  ctx.currentStore?.span?.finish()
11
11
  }
12
12
 
13
+ start (ctx) {
14
+ if (!this.config.dsmEnabled) return
15
+ const { span } = ctx.currentStore
16
+ this.setProducerCheckpoint(span, ctx)
17
+ }
18
+
13
19
  bindStart (ctx) {
14
20
  const { resource, meta } = this.getSpanData(ctx)
15
21
  const span = this.startSpan({
@@ -25,10 +31,6 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
25
31
 
26
32
  this.injectTraceContext(span, ctx)
27
33
 
28
- if (this.config.dsmEnabled) {
29
- this.setProducerCheckpoint(span, ctx)
30
- }
31
-
32
34
  return ctx.currentStore
33
35
  }
34
36
 
@@ -40,13 +42,24 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
40
42
  throw new Error('injectTraceContext must be implemented by subclass')
41
43
  }
42
44
 
45
+ _injectIntoOpts (span, opts) {
46
+ const carrier = {}
47
+ this.tracer.inject(span, 'text_map', carrier)
48
+ const existing = opts.telemetry?.metadata ? JSON.parse(opts.telemetry.metadata) : {}
49
+ existing._datadog = carrier
50
+ opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
51
+ }
52
+
43
53
  setProducerCheckpoint (span, ctx) {
44
- const { queueName, payloadSize, injectTarget } = this.getDsmData(ctx)
54
+ const { queueName, payloadSize, optsTarget } = this.getDsmData(ctx)
45
55
  const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
46
56
  const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
47
- if (injectTarget && typeof injectTarget === 'object') {
48
- injectTarget._datadog = injectTarget._datadog || {}
49
- DsmPathwayCodec.encode(dataStreamsContext, injectTarget._datadog)
57
+
58
+ if (optsTarget && typeof optsTarget === 'object') {
59
+ const existing = optsTarget.telemetry?.metadata ? JSON.parse(optsTarget.telemetry.metadata) : {}
60
+ DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
61
+ if (!existing._datadog) existing._datadog = {}
62
+ optsTarget.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
50
63
  }
51
64
  }
52
65
 
@@ -68,12 +81,22 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
68
81
  }
69
82
  }
70
83
 
71
- injectTraceContext (span, ctx) {
72
- const data = ctx.arguments?.[1]
73
- if (data?.constructor?.name === 'Object') {
74
- data._datadog = data._datadog || {}
75
- this.tracer.inject(span, 'text_map', data._datadog)
84
+ #ensureOpts (ctx) {
85
+ let opts = ctx.arguments?.[2]
86
+ if (!opts || typeof opts !== 'object') {
87
+ opts = {}
88
+ if (ctx.arguments.length <= 2) {
89
+ Array.prototype.push.call(ctx.arguments, opts)
90
+ } else {
91
+ ctx.arguments[2] = opts
92
+ }
76
93
  }
94
+ return opts
95
+ }
96
+
97
+ injectTraceContext (span, ctx) {
98
+ const opts = this.#ensureOpts(ctx)
99
+ this._injectIntoOpts(span, opts)
77
100
  }
78
101
 
79
102
  getDsmData (ctx) {
@@ -81,7 +104,7 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
81
104
  return {
82
105
  queueName: ctx.self?.name || 'bullmq',
83
106
  payloadSize: data ? getMessageSize(data) : 0,
84
- injectTarget: data,
107
+ optsTarget: this.#ensureOpts(ctx),
85
108
  }
86
109
  }
87
110
  }
@@ -108,10 +131,11 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
108
131
  injectTraceContext (span, ctx) {
109
132
  const jobs = ctx.arguments?.[0]
110
133
  if (!Array.isArray(jobs)) return
134
+
111
135
  for (const job of jobs) {
112
- if (job?.data?.constructor?.name !== 'Object') continue
113
- job.data._datadog = job.data._datadog || {}
114
- this.tracer.inject(span, 'text_map', job.data._datadog)
136
+ if (!job) continue
137
+ job.opts = job.opts || {}
138
+ this._injectIntoOpts(span, job.opts)
115
139
  }
116
140
  }
117
141
 
@@ -123,7 +147,7 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
123
147
  return {
124
148
  queueName: ctx.self?.name || 'bullmq',
125
149
  payloadSize,
126
- injectTarget: jobs[0]?.data,
150
+ optsTarget: jobs[0]?.opts,
127
151
  }
128
152
  }
129
153
 
@@ -133,12 +157,14 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
133
157
  const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
134
158
 
135
159
  for (const job of jobs) {
136
- if (job?.data && job.data !== null && job.data.constructor.name === 'Object') {
137
- const payloadSize = getMessageSize(job.data)
138
- const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
139
- job.data._datadog = job.data._datadog || {}
140
- DsmPathwayCodec.encode(dataStreamsContext, job.data._datadog)
141
- }
160
+ if (!job?.data) continue
161
+ const payloadSize = getMessageSize(job.data)
162
+ const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
163
+ job.opts = job.opts || {}
164
+ const existing = job.opts.telemetry?.metadata ? JSON.parse(job.opts.telemetry.metadata) : {}
165
+ DsmPathwayCodec.encode(dataStreamsContext, existing._datadog || existing)
166
+ if (!existing._datadog) existing._datadog = {}
167
+ job.opts.telemetry = { metadata: JSON.stringify(existing), omitContext: true }
142
168
  }
143
169
  }
144
170
  }
@@ -159,18 +185,21 @@ class FlowProducerAddPlugin extends BaseBullmqProducerPlugin {
159
185
 
160
186
  injectTraceContext (span, ctx) {
161
187
  const flow = ctx.arguments?.[0]
162
- if (flow?.data?.constructor?.name === 'Object') {
163
- flow.data._datadog = flow.data._datadog || {}
164
- this.tracer.inject(span, 'text_map', flow.data._datadog)
165
- }
188
+ if (!flow) return
189
+ flow.opts = flow.opts || {}
190
+ this._injectIntoOpts(span, flow.opts)
166
191
  }
167
192
 
168
193
  getDsmData (ctx) {
169
194
  const flow = ctx.arguments?.[0]
195
+ if (!flow) {
196
+ return { queueName: 'bullmq', payloadSize: 0, optsTarget: undefined }
197
+ }
198
+ flow.opts = flow.opts || {}
170
199
  return {
171
- queueName: flow?.queueName || 'bullmq',
172
- payloadSize: flow?.data ? getMessageSize(flow.data) : 0,
173
- injectTarget: flow?.data,
200
+ queueName: flow.queueName || 'bullmq',
201
+ payloadSize: flow.data ? getMessageSize(flow.data) : 0,
202
+ optsTarget: flow.opts,
174
203
  }
175
204
  }
176
205
  }
@@ -128,12 +128,15 @@ class CucumberPlugin extends CiPlugin {
128
128
  const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
129
129
  const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
130
130
 
131
- const testSuiteMetadata = getTestSuiteCommonTags(
132
- this.command,
133
- this.frameworkVersion,
134
- testSuitePath,
135
- 'cucumber'
136
- )
131
+ const testSuiteMetadata = {
132
+ ...getTestSuiteCommonTags(
133
+ this.command,
134
+ this.frameworkVersion,
135
+ testSuitePath,
136
+ 'cucumber'
137
+ ),
138
+ ...this.getSessionRequestErrorTags(),
139
+ }
137
140
  if (isUnskippable) {
138
141
  this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
139
142
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
@@ -46,6 +46,8 @@ const {
46
46
  TEST_RETRY_REASON_TYPES,
47
47
  getPullRequestDiff,
48
48
  getModifiedFilesFromDiff,
49
+ getSessionRequestErrorTags,
50
+ DD_CI_LIBRARY_CONFIGURATION_ERROR,
49
51
  TEST_IS_MODIFIED,
50
52
  getPullRequestBaseBranch,
51
53
  } = require('../../dd-trace/src/plugins/util/test')
@@ -306,6 +308,9 @@ class CypressPlugin {
306
308
 
307
309
  this.isTestIsolationEnabled = getIsTestIsolationEnabled(cypressConfig)
308
310
 
311
+ const envFlushWait = Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS'))
312
+ this.rumFlushWaitMillis = Number.isFinite(envFlushWait) ? envFlushWait : undefined
313
+
309
314
  if (!this.isTestIsolationEnabled) {
310
315
  log.warn('Test isolation is disabled, retries will not be enabled')
311
316
  }
@@ -314,10 +319,15 @@ class CypressPlugin {
314
319
  this.testEnvironmentMetadata[DD_TEST_IS_USER_PROVIDED_SERVICE] =
315
320
  tracer._tracer._config.isServiceUserProvided ? 'true' : 'false'
316
321
 
322
+ this._pendingRequestErrorTags = []
317
323
  this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
318
324
  .then((libraryConfigurationResponse) => {
319
325
  if (libraryConfigurationResponse.err) {
320
326
  log.error('Cypress plugin library config response error', libraryConfigurationResponse.err)
327
+ this._pendingRequestErrorTags.push({
328
+ tag: DD_CI_LIBRARY_CONFIGURATION_ERROR,
329
+ value: 'true',
330
+ })
321
331
  } else {
322
332
  const {
323
333
  libraryConfig: {
@@ -412,6 +422,7 @@ class CypressPlugin {
412
422
  if (this.testSessionSpan && this.testModuleSpan) {
413
423
  testSuiteTags[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
414
424
  testSuiteTags[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
425
+ Object.assign(testSuiteTags, this.getSessionRequestErrorTags())
415
426
  // If testSuiteSpan couldn't be created, we'll use the testModuleSpan as the parent
416
427
  if (!this.testSuiteSpan) {
417
428
  testSuiteTags[TEST_SUITE_ID] = this.testModuleSpan.context().toSpanId()
@@ -468,6 +479,14 @@ class CypressPlugin {
468
479
  })
469
480
  }
470
481
 
482
+ /**
483
+ * Returns request error tags from the test session span for propagation to test spans.
484
+ * @returns {Record<string, string>}
485
+ */
486
+ getSessionRequestErrorTags () {
487
+ return getSessionRequestErrorTags(this.testSessionSpan)
488
+ }
489
+
471
490
  ciVisEvent (name, testLevel, tags = {}) {
472
491
  incrementCountMetric(name, {
473
492
  testLevel,
@@ -596,14 +615,20 @@ class CypressPlugin {
596
615
  },
597
616
  integrationName: TEST_FRAMEWORK_NAME,
598
617
  })
618
+ for (const { tag, value } of this._pendingRequestErrorTags) {
619
+ this.testSessionSpan.setTag(tag, value)
620
+ }
621
+ this._pendingRequestErrorTags = []
599
622
  this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
600
623
 
624
+ const sessionRequestErrorTags = getSessionRequestErrorTags(this.testSessionSpan)
601
625
  this.testModuleSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
602
626
  childOf: this.testSessionSpan,
603
627
  tags: {
604
628
  [COMPONENT]: TEST_FRAMEWORK_NAME,
605
629
  ...this.testEnvironmentMetadata,
606
630
  ...testModuleSpanMetadata,
631
+ ...sessionRequestErrorTags,
607
632
  },
608
633
  integrationName: TEST_FRAMEWORK_NAME,
609
634
  })
@@ -823,6 +848,7 @@ class CypressPlugin {
823
848
  isModifiedTest: this.getIsTestModified(testSuiteAbsolutePath),
824
849
  repositoryRoot: this.repositoryRoot,
825
850
  isTestIsolationEnabled: this.isTestIsolationEnabled,
851
+ rumFlushWaitMillis: this.rumFlushWaitMillis,
826
852
  }
827
853
 
828
854
  if (this.testSuiteSpan) {
@@ -1,5 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
4
+ let rumFlushWaitMillis = 500
5
+
3
6
  let isEarlyFlakeDetectionEnabled = false
4
7
  let isKnownTestsEnabled = false
5
8
  let knownTestsForSuite = []
@@ -15,9 +18,10 @@ const retryReasonsByTestName = new Map()
15
18
  // Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
16
19
  const quarantinedTestErrors = new Map()
17
20
 
18
- // We need to grab the original window as soon as possible,
19
- // in case the test changes the origin. If the test does change the origin,
20
- // any call to `cy.window()` will result in a cross origin error.
21
+ // Track the most recently loaded window in the AUT. Updated via the 'window:load'
22
+ // event so we always get the real app window (after cy.visit()), not the
23
+ // about:blank window that exists when beforeEach runs. If the test later navigates
24
+ // to a cross-origin URL, safeGetRum() handles the access error.
21
25
  let originalWindow
22
26
 
23
27
  // If the test is using multi domain with cy.origin, trying to access
@@ -165,18 +169,45 @@ beforeEach(function () {
165
169
  retryReasonsByTestName.delete(testName)
166
170
  }
167
171
 
172
+ cy.on('window:load', (win) => {
173
+ originalWindow = win
174
+ })
175
+
168
176
  cy.task('dd:beforeEach', {
169
177
  testName,
170
178
  testSuite: Cypress.mocha.getRootSuite().file,
171
179
  }).then(({ traceId, shouldSkip }) => {
172
- Cypress.env('traceId', traceId)
180
+ if (traceId) {
181
+ cy.setCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME, traceId).then(() => {
182
+ // When testIsolation:false, the page is not reset between tests, so the RUM session
183
+ // stopped in afterEach must be explicitly restarted so events in this test are
184
+ // associated with the new testExecutionId.
185
+ //
186
+ // After stopSession(), the RUM SDK creates a new session upon a user interaction
187
+ // (click, scroll, keydown, or touchstart). We dispatch a synthetic click on the window
188
+ // to trigger session renewal, then call startView() to establish a view boundary.
189
+ if (!isTestIsolationEnabled && originalWindow) {
190
+ const rum = safeGetRum(originalWindow)
191
+ if (rum) {
192
+ try {
193
+ const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
194
+ // The browser-sdk addEventListener wrapper filters out untrusted synthetic events
195
+ // unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
196
+ // See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
197
+ Object.defineProperty(evt, '__ddIsTrusted', { value: true })
198
+ originalWindow.dispatchEvent(evt)
199
+ } catch {}
200
+ if (rum.startView) {
201
+ rum.startView()
202
+ }
203
+ }
204
+ }
205
+ })
206
+ }
173
207
  if (shouldSkip) {
174
208
  this.skip()
175
209
  }
176
210
  })
177
- cy.window().then(win => {
178
- originalWindow = win
179
- })
180
211
  })
181
212
 
182
213
  before(function () {
@@ -195,6 +226,9 @@ before(function () {
195
226
  isImpactedTestsEnabled = suiteConfig.isImpactedTestsEnabled
196
227
  isModifiedTest = suiteConfig.isModifiedTest
197
228
  isTestIsolationEnabled = suiteConfig.isTestIsolationEnabled
229
+ if (Number.isFinite(suiteConfig.rumFlushWaitMillis)) {
230
+ rumFlushWaitMillis = suiteConfig.rumFlushWaitMillis
231
+ }
198
232
  }
199
233
  })
200
234
  })
@@ -241,8 +275,14 @@ afterEach(function () {
241
275
  testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
242
276
  } catch {}
243
277
 
244
- if (safeGetRum(originalWindow)) {
278
+ const rum = safeGetRum(originalWindow)
279
+ if (rum) {
245
280
  testInfo.isRUMActive = true
281
+ if (rum.stopSession) {
282
+ rum.stopSession()
283
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
284
+ cy.wait(rumFlushWaitMillis)
285
+ }
246
286
  }
247
287
  let coverage
248
288
  try {
@@ -182,9 +182,10 @@ class JestPlugin extends CiPlugin {
182
182
  config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
183
183
  config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
184
184
  config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
185
+ config._ddRequestErrorTags = this.getSessionRequestErrorTags()
185
186
  config._ddItrCorrelationId = this.itrCorrelationId
186
187
  config._ddIsEarlyFlakeDetectionEnabled = !!this.libraryConfig?.isEarlyFlakeDetectionEnabled
187
- config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
188
+ config._ddEarlyFlakeDetectionSlowTestRetries = this.libraryConfig?.earlyFlakeDetectionSlowTestRetries ?? {}
188
189
  config._ddRepositoryRoot = this.repositoryRoot
189
190
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
190
191
  config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
@@ -208,6 +209,7 @@ class JestPlugin extends CiPlugin {
208
209
  _ddTestSessionId: testSessionId,
209
210
  _ddTestCommand: testCommand,
210
211
  _ddTestModuleId: testModuleId,
212
+ _ddRequestErrorTags: requestErrorTags,
211
213
  _ddItrCorrelationId: itrCorrelationId,
212
214
  _ddForcedToRun,
213
215
  _ddUnskippable,
@@ -219,7 +221,11 @@ class JestPlugin extends CiPlugin {
219
221
  'x-datadog-parent-id': testModuleId,
220
222
  })
221
223
 
222
- const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
224
+ const testSuiteMetadata = {
225
+ ...getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest'),
226
+ // requestErrorTags from test env options may be undefined
227
+ ...(requestErrorTags !== undefined && requestErrorTags !== null ? requestErrorTags : {}),
228
+ }
223
229
 
224
230
  if (_ddUnskippable) {
225
231
  const unskippableSuites = this.getUnskippableSuites(_ddUnskippable)
@@ -389,6 +395,7 @@ class JestPlugin extends CiPlugin {
389
395
  attemptToFixFailed,
390
396
  isAtrRetry,
391
397
  finalStatus,
398
+ earlyFlakeAbortReason,
392
399
  }) => {
393
400
  span.setTag(TEST_STATUS, status)
394
401
  if (finalStatus) {
@@ -409,6 +416,9 @@ class JestPlugin extends CiPlugin {
409
416
  span.setTag(TEST_IS_RETRY, 'true')
410
417
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
411
418
  }
419
+ if (earlyFlakeAbortReason) {
420
+ span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
421
+ }
412
422
 
413
423
  this.telemetry.ciVisEvent(
414
424
  TELEMETRY_EVENT_FINISHED,
@@ -3,7 +3,7 @@
3
3
  const { readFileSync } = require('fs')
4
4
  const { parse } = require('../../../vendor/dist/jest-docblock')
5
5
 
6
- const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
6
+ const { getTestSuitePath, getEfdRetryCount } = require('../../dd-trace/src/plugins/util/test')
7
7
  const log = require('../../dd-trace/src/log')
8
8
 
9
9
  /**
@@ -172,4 +172,5 @@ module.exports = {
172
172
  getJestTestName,
173
173
  getJestSuitesToRun,
174
174
  isMarkedAsUnskippable,
175
+ getEfdRetryCount,
175
176
  }
@@ -40,14 +40,18 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
40
40
  * @returns {ConsumerBacklog}
41
41
  */
42
42
  transformCommit (commit) {
43
- const { groupId, partition, offset, topic } = commit
44
- return {
43
+ const { groupId, partition, offset, topic, clusterId } = commit
44
+ const backlog = {
45
45
  partition,
46
46
  topic,
47
47
  type: 'kafka_commit',
48
48
  offset: Number(offset),
49
49
  consumer_group: groupId,
50
50
  }
51
+ if (clusterId) {
52
+ backlog.kafka_cluster_id = clusterId
53
+ }
54
+ return backlog
51
55
  }
52
56
 
53
57
  commit (commitList) {
@@ -65,6 +69,22 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
65
69
  }
66
70
  }
67
71
 
72
+ start (ctx) {
73
+ if (!this.config.dsmEnabled) return
74
+ const { topic, message, groupId, clusterId } = ctx.extractedArgs || ctx
75
+ const headers = convertToTextMap(message?.headers)
76
+ if (!headers) return
77
+
78
+ const { span } = ctx.currentStore
79
+ const payloadSize = getMessageSize(message)
80
+ this.tracer.decodeDataStreamsContext(headers)
81
+ const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
82
+ if (clusterId) {
83
+ edgeTags.push(`kafka_cluster_id:${clusterId}`)
84
+ }
85
+ this.tracer.setCheckpoint(edgeTags, span, payloadSize)
86
+ }
87
+
68
88
  bindStart (ctx) {
69
89
  const { topic, partition, message, groupId, clusterId } = ctx.extractedArgs || ctx
70
90
 
@@ -89,16 +109,6 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
89
109
  }, ctx)
90
110
  if (message?.offset) span.setTag('kafka.message.offset', message?.offset)
91
111
 
92
- if (this.config.dsmEnabled && headers) {
93
- const payloadSize = getMessageSize(message)
94
- this.tracer.decodeDataStreamsContext(headers)
95
- const edgeTags = ['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka']
96
- if (clusterId) {
97
- edgeTags.push(`kafka_cluster_id:${clusterId}`)
98
- }
99
- this.tracer.setCheckpoint(edgeTags, span, payloadSize)
100
- }
101
-
102
112
  if (afterStartCh.hasSubscribers) {
103
113
  afterStartCh.publish({ topic, partition, message, groupId, currentStore: ctx.currentStore })
104
114
  }
@@ -37,16 +37,20 @@ class KafkajsProducerPlugin extends ProducerPlugin {
37
37
  * @param {ProducerResponseItem} response
38
38
  * @returns {ProducerBacklog}
39
39
  */
40
- transformProduceResponse (response) {
40
+ transformProduceResponse (response, clusterId) {
41
41
  // In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
42
42
  const { topicName: topic, partition, offset, baseOffset } = response
43
43
  const offsetAsLong = offset || baseOffset
44
- return {
44
+ const backlog = {
45
45
  type: 'kafka_produce',
46
46
  partition,
47
47
  offset: offsetAsLong ? Number(offsetAsLong) : undefined,
48
48
  topic,
49
49
  }
50
+ if (clusterId) {
51
+ backlog.kafka_cluster_id = clusterId
52
+ }
53
+ return backlog
50
54
  }
51
55
 
52
56
  /**
@@ -56,6 +60,7 @@ class KafkajsProducerPlugin extends ProducerPlugin {
56
60
  */
57
61
  commit (ctx) {
58
62
  const commitList = ctx.result
63
+ const clusterId = ctx.clusterId
59
64
 
60
65
  if (!this.config.dsmEnabled) return
61
66
  if (!commitList || !Array.isArray(commitList)) return
@@ -65,12 +70,33 @@ class KafkajsProducerPlugin extends ProducerPlugin {
65
70
  'offset',
66
71
  'topic',
67
72
  ]
68
- for (const commit of commitList.map(this.transformProduceResponse)) {
73
+ for (const commit of commitList.map(r => this.transformProduceResponse(r, clusterId))) {
69
74
  if (keys.some(key => !commit.hasOwnProperty(key))) continue
70
75
  this.tracer.setOffset(commit)
71
76
  }
72
77
  }
73
78
 
79
+ start (ctx) {
80
+ if (!this.config.dsmEnabled) return
81
+ const { topic, messages, clusterId, disableHeaderInjection, currentStore: { span } } = ctx
82
+
83
+ for (const message of messages) {
84
+ if (message !== null && typeof message === 'object') {
85
+ const payloadSize = getMessageSize(message)
86
+ const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
87
+
88
+ if (clusterId) {
89
+ edgeTags.push(`kafka_cluster_id:${clusterId}`)
90
+ }
91
+
92
+ const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
93
+ if (!disableHeaderInjection) {
94
+ DsmPathwayCodec.encode(dataStreamsContext, message.headers)
95
+ }
96
+ }
97
+ }
98
+ }
99
+
74
100
  bindStart (ctx) {
75
101
  const { topic, messages, bootstrapServers, clusterId, disableHeaderInjection } = ctx
76
102
  const span = this.startSpan({
@@ -89,25 +115,10 @@ class KafkajsProducerPlugin extends ProducerPlugin {
89
115
  span.setTag(BOOTSTRAP_SERVERS_KEY, bootstrapServers)
90
116
  }
91
117
  for (const message of messages) {
92
- if (message !== null && typeof message === 'object') {
93
- // message headers are not supported for kafka broker versions <0.11
94
- if (!disableHeaderInjection) {
95
- message.headers ??= {}
96
- this.tracer.inject(span, 'text_map', message.headers)
97
- }
98
- if (this.config.dsmEnabled) {
99
- const payloadSize = getMessageSize(message)
100
- const edgeTags = ['direction:out', `topic:${topic}`, 'type:kafka']
101
-
102
- if (clusterId) {
103
- edgeTags.push(`kafka_cluster_id:${clusterId}`)
104
- }
105
-
106
- const dataStreamsContext = this.tracer.setCheckpoint(edgeTags, span, payloadSize)
107
- if (!disableHeaderInjection) {
108
- DsmPathwayCodec.encode(dataStreamsContext, message.headers)
109
- }
110
- }
118
+ // message headers are not supported for kafka broker versions <0.11
119
+ if (message !== null && typeof message === 'object' && !disableHeaderInjection) {
120
+ message.headers ??= {}
121
+ this.tracer.inject(span, 'text_map', message.headers)
111
122
  }
112
123
  }
113
124
 
@@ -93,12 +93,15 @@ class MochaPlugin extends CiPlugin {
93
93
  return
94
94
  }
95
95
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
96
- const testSuiteMetadata = getTestSuiteCommonTags(
97
- this.command,
98
- this.frameworkVersion,
99
- testSuite,
100
- 'mocha'
101
- )
96
+ const testSuiteMetadata = {
97
+ ...getTestSuiteCommonTags(
98
+ this.command,
99
+ this.frameworkVersion,
100
+ testSuite,
101
+ 'mocha'
102
+ ),
103
+ ...this.getSessionRequestErrorTags(),
104
+ }
102
105
  if (isUnskippable) {
103
106
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
104
107
  this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
@@ -120,12 +120,15 @@ class PlaywrightPlugin extends CiPlugin {
120
120
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
121
121
  const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
122
122
 
123
- const testSuiteMetadata = getTestSuiteCommonTags(
124
- this.command,
125
- this.frameworkVersion,
126
- testSuite,
127
- 'playwright'
128
- )
123
+ const testSuiteMetadata = {
124
+ ...getTestSuiteCommonTags(
125
+ this.command,
126
+ this.frameworkVersion,
127
+ testSuite,
128
+ 'playwright'
129
+ ),
130
+ ...this.getSessionRequestErrorTags(),
131
+ }
129
132
  if (testSourceFile) {
130
133
  testSuiteMetadata[TEST_SOURCE_FILE] = testSourceFile
131
134
  testSuiteMetadata[TEST_SOURCE_START] = 1
@@ -222,6 +225,7 @@ class PlaywrightPlugin extends CiPlugin {
222
225
  // for a test session. They can be passed the same way `DD_PLAYWRIGHT_WORKER` is passed.
223
226
  formattedSpan.meta[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
224
227
  formattedSpan.meta[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
228
+ Object.assign(formattedSpan.meta, this.getSessionRequestErrorTags())
225
229
  formattedSpan.meta[TEST_COMMAND] = this.command
226
230
  formattedSpan.meta[TEST_MODULE] = this.constructor.id
227
231
  // MISSING _trace.startTime and _trace.ticks - because by now the suite is already serialized