dd-trace 5.71.0 → 5.73.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 (84) hide show
  1. package/LICENSE-3rdparty.csv +7 -0
  2. package/index.d.ts +114 -1
  3. package/package.json +25 -4
  4. package/packages/datadog-esbuild/index.js +8 -0
  5. package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
  6. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  7. package/packages/datadog-instrumentations/src/azure-service-bus.js +49 -22
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +2 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +7 -7
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  11. package/packages/datadog-instrumentations/src/jest.js +85 -47
  12. package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -5
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  15. package/packages/datadog-instrumentations/src/pg.js +1 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +5 -5
  17. package/packages/datadog-instrumentations/src/vitest.js +8 -8
  18. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
  20. package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
  21. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
  22. package/packages/datadog-plugin-azure-functions/src/index.js +50 -3
  23. package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
  24. package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
  25. package/packages/datadog-plugin-cucumber/src/index.js +3 -3
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
  27. package/packages/datadog-plugin-jest/src/index.js +53 -18
  28. package/packages/datadog-plugin-jest/src/util.js +10 -2
  29. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  30. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  31. package/packages/datadog-plugin-vitest/src/index.js +2 -2
  32. package/packages/datadog-plugin-ws/src/close.js +1 -1
  33. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  34. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  35. package/packages/datadog-plugin-ws/src/server.js +5 -3
  36. package/packages/dd-trace/src/appsec/index.js +9 -1
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -3
  38. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
  39. package/packages/dd-trace/src/config.js +108 -26
  40. package/packages/dd-trace/src/config_defaults.js +12 -0
  41. package/packages/dd-trace/src/git_properties.js +90 -5
  42. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
  43. package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
  44. package/packages/dd-trace/src/llmobs/sdk.js +20 -4
  45. package/packages/dd-trace/src/llmobs/tagger.js +12 -0
  46. package/packages/dd-trace/src/noop/proxy.js +3 -0
  47. package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
  48. package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
  49. package/packages/dd-trace/src/openfeature/index.js +77 -0
  50. package/packages/dd-trace/src/openfeature/noop.js +101 -0
  51. package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
  52. package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
  53. package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
  54. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
  55. package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
  56. package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
  57. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
  58. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +53 -0
  59. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +252 -0
  60. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +116 -0
  61. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +226 -0
  62. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +78 -0
  63. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
  64. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
  65. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
  66. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
  67. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +66 -0
  68. package/packages/dd-trace/src/opentelemetry/otlp/resource.proto +45 -0
  69. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
  70. package/packages/dd-trace/src/plugins/index.js +1 -0
  71. package/packages/dd-trace/src/plugins/util/test.js +6 -5
  72. package/packages/dd-trace/src/profiling/config.js +21 -1
  73. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
  74. package/packages/dd-trace/src/profiling/profiler.js +44 -22
  75. package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
  76. package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
  77. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
  78. package/packages/dd-trace/src/proxy.js +22 -1
  79. package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
  80. package/packages/dd-trace/src/remote_config/index.js +1 -0
  81. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
  82. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  83. package/packages/dd-trace/src/supported-configurations.json +18 -0
  84. package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const ProducerPlugin = require('./producer')
4
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
5
+
6
+ class AzureEventHubsPlugin extends CompositePlugin {
7
+ static get id () { return 'azure-event-hubs' }
8
+ static get plugins () {
9
+ return {
10
+ producer: ProducerPlugin
11
+ }
12
+ }
13
+ }
14
+
15
+ module.exports = AzureEventHubsPlugin
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
4
+ const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
5
+
6
+ class AzureEventHubsProducerPlugin extends ProducerPlugin {
7
+ static get id () { return 'azure-event-hubs' }
8
+ static get operation () { return 'send' }
9
+ static get prefix () { return 'tracing:apm:azure-event-hubs:send' }
10
+
11
+ bindStart (ctx) {
12
+ // we do not want to make these spans when batch linking is disabled.
13
+ if (!batchLinksAreEnabled() && ctx.functionName === 'tryAdd') {
14
+ return ctx.currentStore
15
+ }
16
+
17
+ const qualifiedNamespace = ctx.config.endpoint.replace('sb://', '').replace('/', '')
18
+ const entityPath = ctx.config.entityPath
19
+ const span = this.startSpan({
20
+ resource: entityPath,
21
+ type: 'messaging',
22
+ meta: {
23
+ component: 'azure-event-hubs',
24
+ 'messaging.system': 'eventhubs',
25
+ 'messaging.destination.name': entityPath,
26
+ 'network.destination.name': qualifiedNamespace,
27
+ }
28
+ }, ctx)
29
+
30
+ if (ctx.functionName === 'tryAdd') {
31
+ span._spanContext._name = 'azure.eventhubs.create'
32
+ span.setTag('messaging.operation', 'create')
33
+
34
+ if (ctx.eventData.messageID !== undefined) {
35
+ span.setTag('message.id', ctx.eventData.messageID)
36
+ }
37
+
38
+ if (batchLinksAreEnabled()) {
39
+ ctx.batch._spanContexts.push(span.context())
40
+ injectTraceContext(this.tracer, span, ctx.eventData)
41
+ }
42
+ }
43
+
44
+ if (ctx.functionName === 'sendBatch') {
45
+ const eventData = ctx.eventData
46
+ const eventDataLength = eventData.length || eventData._context.connection._eventsCount
47
+ span.setTag('messaging.operation', 'send')
48
+ span.setTag('messaging.batch.message_count', eventDataLength)
49
+
50
+ if (eventData.constructor.name !== 'EventDataBatchImpl' && Array.isArray(eventData)) {
51
+ eventData.forEach(event => {
52
+ injectTraceContext(this.tracer, span, event)
53
+ })
54
+ } else {
55
+ if (batchLinksAreEnabled()) {
56
+ eventData._spanContexts.forEach(spanContext => {
57
+ span.addLink(spanContext)
58
+ })
59
+ }
60
+ }
61
+ }
62
+ return ctx.currentStore
63
+ }
64
+
65
+ asyncEnd (ctx) {
66
+ super.finish()
67
+ }
68
+ }
69
+
70
+ function injectTraceContext (tracer, span, event) {
71
+ if (!event.properties) {
72
+ event.properties = {}
73
+ }
74
+ tracer.inject(span, 'text_map', event.properties)
75
+ }
76
+
77
+ function batchLinksAreEnabled () {
78
+ const eh = getEnvironmentVariable('DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED')
79
+ return eh !== 'false'
80
+ }
81
+
82
+ module.exports = AzureEventHubsProducerPlugin
@@ -13,6 +13,7 @@ const triggerMap = {
13
13
  put: 'Http',
14
14
  serviceBusQueue: 'ServiceBus',
15
15
  serviceBusTopic: 'ServiceBus',
16
+ eventHub: 'EventHubs',
16
17
  }
17
18
 
18
19
  class AzureFunctionsPlugin extends TracingPlugin {
@@ -23,8 +24,10 @@ class AzureFunctionsPlugin extends TracingPlugin {
23
24
  static prefix = 'tracing:datadog:azure:functions:invoke'
24
25
 
25
26
  bindStart (ctx) {
26
- const childOf = extractTraceContext(this._tracer, ctx)
27
27
  const meta = getMetaForTrigger(ctx)
28
+ const triggerType = triggerMap[ctx.methodName]
29
+ const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
30
+ const childOf = isMessagingService ? null : extractTraceContext(this._tracer, ctx)
28
31
  const span = this.startSpan(this.operationName(), {
29
32
  childOf,
30
33
  service: this.serviceName(),
@@ -32,6 +35,10 @@ class AzureFunctionsPlugin extends TracingPlugin {
32
35
  meta,
33
36
  }, ctx)
34
37
 
38
+ if (isMessagingService) {
39
+ setSpanLinks(triggerType, this.tracer, span, ctx)
40
+ }
41
+
35
42
  ctx.span = span
36
43
  return ctx.currentStore
37
44
  }
@@ -86,6 +93,16 @@ function getMetaForTrigger ({ functionName, methodName, invocationContext }) {
86
93
  'resource.name': `ServiceBus ${functionName}`,
87
94
  'span.kind': 'consumer'
88
95
  }
96
+ } else if (triggerMap[methodName] === 'EventHubs') {
97
+ const partitionContext = invocationContext.triggerMetadata.triggerPartitionContext
98
+ meta = {
99
+ ...meta,
100
+ 'messaging.destination.name': partitionContext.eventHubName,
101
+ 'messaging.operation': 'receive',
102
+ 'messaging.system': 'eventhubs',
103
+ 'resource.name': `EventHubs ${functionName}`,
104
+ 'span.kind': 'consumer'
105
+ }
89
106
  }
90
107
 
91
108
  return meta
@@ -99,8 +116,38 @@ function extractTraceContext (tracer, ctx) {
99
116
  switch (String(triggerMap[ctx.methodName])) {
100
117
  case 'Http':
101
118
  return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
102
- case 'ServiceBus':
103
- return tracer.extract('text_map', ctx.invocationContext.triggerMetadata.applicationProperties)
119
+ default:
120
+ null
121
+ }
122
+ }
123
+
124
+ // message & messages & batch with cardinality of 1 == applicationProperties
125
+ // messages with cardinality of many == applicationPropertiesArray
126
+ function setSpanLinks (triggerType, tracer, span, ctx) {
127
+ const cardinality = ctx.invocationContext.options.trigger.cardinality
128
+ const triggerMetadata = ctx.invocationContext.triggerMetadata
129
+ const isServiceBus = triggerType === 'ServiceBus'
130
+
131
+ const properties = isServiceBus
132
+ ? triggerMetadata.applicationProperties
133
+ : triggerMetadata.properties
134
+
135
+ const propertiesArray = isServiceBus
136
+ ? triggerMetadata.applicationPropertiesArray
137
+ : triggerMetadata.propertiesArray
138
+
139
+ const addLinkFromProperties = (props) => {
140
+ if (!props || Object.keys(props).length === 0) return
141
+ const spanContext = tracer.extract('text_map', props)
142
+ if (spanContext) {
143
+ span.addLink(spanContext)
144
+ }
145
+ }
146
+
147
+ if (cardinality === 'many' && propertiesArray?.length > 0) {
148
+ propertiesArray.forEach(addLinkFromProperties)
149
+ } else if (cardinality === 'one') {
150
+ addLinkFromProperties(properties)
104
151
  }
105
152
  }
106
153
 
@@ -4,7 +4,7 @@ const ProducerPlugin = require('./producer')
4
4
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
5
5
 
6
6
  class AzureServiceBusPlugin extends CompositePlugin {
7
- static id = 'azure-service-bus'
7
+ static get id () { return 'azure-service-bus' }
8
8
  static get plugins () {
9
9
  return {
10
10
  producer: ProducerPlugin
@@ -1,36 +1,84 @@
1
1
  'use strict'
2
2
 
3
+ const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
3
4
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
5
 
5
6
  class AzureServiceBusProducerPlugin extends ProducerPlugin {
6
- static id = 'azure-service-bus'
7
- static operation = 'send'
7
+ static get id () { return 'azure-service-bus' }
8
+ static get operation () { return 'send' }
9
+ static get prefix () { return 'tracing:apm:azure-service-bus:send' }
8
10
 
9
11
  bindStart (ctx) {
10
- const { sender, msg } = ctx
11
- const qualifiedSenderNamespace = sender._sender.audience.replace('sb://', '')
12
+ // we do not want to make these spans when batch linking is disabled.
13
+ if (!batchLinksAreEnabled() && ctx.functionName === 'tryAddMessage') {
14
+ return ctx.currentStore
15
+ }
16
+
17
+ const qualifiedSenderNamespace = ctx.config.host
12
18
  const span = this.startSpan({
13
- resource: sender.entityPath,
19
+ resource: ctx.entityPath,
14
20
  type: 'messaging',
15
21
  meta: {
16
22
  component: 'azure-service-bus',
17
- 'messaging.destination.name': sender.entityPath,
23
+ 'messaging.destination.name': ctx.entityPath,
18
24
  'messaging.operation': 'send',
19
25
  'messaging.system': 'servicebus',
20
26
  'network.destination.name': qualifiedSenderNamespace,
21
27
  }
22
28
  }, ctx)
23
29
 
24
- // This is the correct key for injecting trace context into Azure Service Bus messages
25
- // It may not be present in the message properties, so we ensure it exists
26
- if (!msg.applicationProperties) {
27
- msg.applicationProperties = {}
28
- }
30
+ if (ctx.functionName === 'tryAddMessage') {
31
+ span._spanContext._name = 'azure.servicebus.create'
32
+ span.setTag('messaging.operation', 'create')
29
33
 
30
- this.tracer.inject(span, 'text_map', msg.applicationProperties)
34
+ if (ctx.msg.messageID !== undefined) {
35
+ span.setTag('message.id', ctx.msg)
36
+ }
31
37
 
38
+ if (batchLinksAreEnabled()) {
39
+ ctx.batch._spanContexts.push(span.context())
40
+ injectTraceContext(this.tracer, span, ctx.msg)
41
+ }
42
+ }
43
+
44
+ if (ctx.functionName === 'send' || ctx.functionName === 'sendBatch' || ctx.functionName === 'scheduleMessages') {
45
+ const messages = ctx.msg
46
+ const isBatch = messages.constructor?.name === 'ServiceBusMessageBatchImpl'
47
+ if (isBatch) {
48
+ span.setTag('messaging.batch.message_count', messages.count)
49
+ if (batchLinksAreEnabled()) {
50
+ messages._spanContexts.forEach(spanContext => {
51
+ span.addLink(spanContext)
52
+ })
53
+ }
54
+ } else if (Array.isArray(messages)) {
55
+ span.setTag('messaging.batch.message_count', messages.length)
56
+ messages.forEach(event => {
57
+ injectTraceContext(this.tracer, span, event)
58
+ })
59
+ } else {
60
+ injectTraceContext(this.tracer, span, messages)
61
+ }
62
+ }
32
63
  return ctx.currentStore
33
64
  }
65
+
66
+ asyncEnd (ctx) {
67
+ super.finish()
68
+ }
69
+ }
70
+
71
+ function injectTraceContext (tracer, span, msg) {
72
+ if (!msg.applicationProperties) {
73
+ msg.applicationProperties = {}
74
+ }
75
+
76
+ tracer.inject(span, 'text_map', msg.applicationProperties)
77
+ }
78
+
79
+ function batchLinksAreEnabled () {
80
+ const sb = getEnvironmentVariable('DD_TRACE_AZURE_SERVICEBUS_BATCH_LINKS_ENABLED')
81
+ return sb !== 'false'
34
82
  }
35
83
 
36
84
  module.exports = AzureServiceBusProducerPlugin
@@ -407,7 +407,7 @@ class CucumberPlugin extends CiPlugin {
407
407
  this.addSub('ci:cucumber:is-modified-test', ({
408
408
  scenarios,
409
409
  testFileAbsolutePath,
410
- modifiedTests,
410
+ modifiedFiles,
411
411
  stepIds,
412
412
  stepDefinitions,
413
413
  setIsModified
@@ -418,7 +418,7 @@ class CucumberPlugin extends CiPlugin {
418
418
  testScenarioPath,
419
419
  scenario.location.line,
420
420
  scenario.steps[scenario.steps.length - 1].location.line,
421
- modifiedTests,
421
+ modifiedFiles,
422
422
  'cucumber'
423
423
  )
424
424
  if (isModified) {
@@ -436,7 +436,7 @@ class CucumberPlugin extends CiPlugin {
436
436
  stepDefinition.uri,
437
437
  testStartLineStep,
438
438
  testEndLineStep,
439
- modifiedTests,
439
+ modifiedFiles,
440
440
  'cucumber'
441
441
  )
442
442
  if (isModified) {
@@ -45,7 +45,7 @@ const {
45
45
  getLibraryCapabilitiesTags,
46
46
  TEST_RETRY_REASON_TYPES,
47
47
  getPullRequestDiff,
48
- getModifiedTestsFromDiff,
48
+ getModifiedFilesFromDiff,
49
49
  TEST_IS_MODIFIED,
50
50
  getPullRequestBaseBranch
51
51
  } = require('../../dd-trace/src/plugins/util/test')
@@ -188,7 +188,7 @@ function getTestManagementTests (tracer, testConfiguration) {
188
188
  })
189
189
  }
190
190
 
191
- function getModifiedTests (testEnvironmentMetadata) {
191
+ function getModifiedFiles (testEnvironmentMetadata) {
192
192
  const {
193
193
  [GIT_PULL_REQUEST_BASE_BRANCH]: pullRequestBaseBranch,
194
194
  [GIT_PULL_REQUEST_BASE_BRANCH_SHA]: pullRequestBaseBranchSha,
@@ -199,9 +199,9 @@ function getModifiedTests (testEnvironmentMetadata) {
199
199
 
200
200
  if (baseBranchSha) {
201
201
  const diff = getPullRequestDiff(baseBranchSha, commitHeadSha)
202
- const modifiedTests = getModifiedTestsFromDiff(diff)
203
- if (modifiedTests) {
204
- return modifiedTests
202
+ const modifiedFiles = getModifiedFilesFromDiff(diff)
203
+ if (modifiedFiles) {
204
+ return modifiedFiles
205
205
  }
206
206
  }
207
207
 
@@ -245,7 +245,7 @@ class CypressPlugin {
245
245
  isTestManagementTestsEnabled = false
246
246
  testManagementAttemptToFixRetries = 0
247
247
  isImpactedTestsEnabled = false
248
- modifiedTests = []
248
+ modifiedFiles = []
249
249
 
250
250
  constructor () {
251
251
  const {
@@ -339,10 +339,10 @@ class CypressPlugin {
339
339
 
340
340
  getIsTestModified (testSuiteAbsolutePath) {
341
341
  const relativeTestSuitePath = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
342
- if (!this.modifiedTests) {
342
+ if (!this.modifiedFiles) {
343
343
  return false
344
344
  }
345
- const lines = this.modifiedTests[relativeTestSuitePath]
345
+ const lines = this.modifiedFiles[relativeTestSuitePath]
346
346
  if (!lines) {
347
347
  return false
348
348
  }
@@ -520,7 +520,7 @@ class CypressPlugin {
520
520
 
521
521
  if (this.isImpactedTestsEnabled) {
522
522
  try {
523
- this.modifiedTests = getModifiedTests(this.testEnvironmentMetadata)
523
+ this.modifiedFiles = getModifiedFiles(this.testEnvironmentMetadata)
524
524
  } catch (error) {
525
525
  log.error(error)
526
526
  this.isImpactedTestsEnabled = false
@@ -50,6 +50,7 @@ const {
50
50
  TELEMETRY_CODE_COVERAGE_NUM_FILES,
51
51
  TELEMETRY_TEST_SESSION
52
52
  } = require('../../dd-trace/src/ci-visibility/telemetry')
53
+ const log = require('../../dd-trace/src/log')
53
54
 
54
55
  const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID')
55
56
 
@@ -102,6 +103,7 @@ class JestPlugin extends CiPlugin {
102
103
  }
103
104
  process.on('message', handler)
104
105
  }
106
+ this.testSuiteSpanPerTestSuiteAbsolutePath = new Map()
105
107
 
106
108
  this.addSub('ci:jest:session:finish', ({
107
109
  status,
@@ -196,7 +198,8 @@ class JestPlugin extends CiPlugin {
196
198
  testSourceFile,
197
199
  testEnvironmentOptions,
198
200
  frameworkVersion,
199
- displayName
201
+ displayName,
202
+ testSuiteAbsolutePath
200
203
  }) => {
201
204
  const {
202
205
  _ddTestSessionId: testSessionId,
@@ -259,6 +262,7 @@ class JestPlugin extends CiPlugin {
259
262
  if (_ddTestCodeCoverageEnabled) {
260
263
  this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: 'istanbul' })
261
264
  }
265
+ this.testSuiteSpanPerTestSuiteAbsolutePath.set(testSuiteAbsolutePath, this.testSuiteSpan)
262
266
  })
263
267
 
264
268
  this.addSub('ci:jest:worker-report:coverage', data => {
@@ -272,25 +276,54 @@ class JestPlugin extends CiPlugin {
272
276
  })
273
277
  })
274
278
 
275
- this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
276
- this.testSuiteSpan.setTag(TEST_STATUS, status)
279
+ this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error, testSuiteAbsolutePath }) => {
280
+ const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
281
+ if (!testSuiteSpan) {
282
+ log.warn('"ci:jest:test-suite:finish": no span found for test suite absolute path %s', testSuiteAbsolutePath)
283
+ return
284
+ }
285
+ const hasStatus = testSuiteSpan.context()._tags[TEST_STATUS]
286
+ if (!hasStatus) {
287
+ // The status may have been set in 'ci:jest:test-suite:error'
288
+ testSuiteSpan.setTag(TEST_STATUS, status)
289
+ }
277
290
  if (error) {
278
- this.testSuiteSpan.setTag('error', error)
291
+ testSuiteSpan.setTag('error', error)
292
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
279
293
  } else if (errorMessage) {
280
- this.testSuiteSpan.setTag('error', new Error(errorMessage))
294
+ testSuiteSpan.setTag('error', new Error(errorMessage))
295
+ testSuiteSpan.setTag(TEST_STATUS, 'fail')
281
296
  }
282
- this.testSuiteSpan.finish()
283
- this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
284
- // Suites potentially run in a different process than the session,
285
- // so calling finishAllTraceSpans on the session span is not enough
286
- finishAllTraceSpans(this.testSuiteSpan)
287
- // Flushing within jest workers is cheap, as it's just interprocess communication
288
- // We do not want to flush after every suite if jest is running tests serially,
289
- // as every flush is an HTTP request.
290
- if (isJestWorker) {
291
- this.tracer._exporter.flush()
297
+ // We need to give the potential error in 'ci:jest:test-suite:error' time to be published
298
+ process.nextTick(() => {
299
+ testSuiteSpan.finish()
300
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
301
+ // Suites potentially run in a different process than the session,
302
+ // so calling finishAllTraceSpans on the session span is not enough
303
+ finishAllTraceSpans(testSuiteSpan)
304
+ // Flushing within jest workers is cheap, as it's just interprocess communication
305
+ // We do not want to flush after every suite if jest is running tests serially,
306
+ // as every flush is an HTTP request.
307
+ if (isJestWorker) {
308
+ this.tracer._exporter.flush()
309
+ }
310
+ this.removeAllDiProbes()
311
+ this.testSuiteSpanPerTestSuiteAbsolutePath.delete(testSuiteAbsolutePath)
312
+ })
313
+ })
314
+
315
+ this.addSub('ci:jest:test-suite:error', ({ error, errorMessage, testSuiteAbsolutePath }) => {
316
+ const runningTestSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath)
317
+ if (!runningTestSuiteSpan) {
318
+ log.warn('"ci:jest:test-suite:error": no span found for test suite absolute path %s', testSuiteAbsolutePath)
319
+ return
320
+ }
321
+ if (error) {
322
+ runningTestSuiteSpan.setTag('error', error)
323
+ } else if (errorMessage) {
324
+ runningTestSuiteSpan.setTag('error', new Error(errorMessage))
292
325
  }
293
- this.removeAllDiProbes()
326
+ runningTestSuiteSpan.setTag(TEST_STATUS, 'fail')
294
327
  })
295
328
 
296
329
  /**
@@ -420,7 +453,8 @@ class JestPlugin extends CiPlugin {
420
453
  isJestRetry,
421
454
  isDisabled,
422
455
  isQuarantined,
423
- isModified
456
+ isModified,
457
+ testSuiteAbsolutePath
424
458
  } = test
425
459
 
426
460
  const extraTags = {
@@ -468,8 +502,9 @@ class JestPlugin extends CiPlugin {
468
502
  if (isNew) {
469
503
  extraTags[TEST_IS_NEW] = 'true'
470
504
  }
505
+ const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath) || this.testSuiteSpan
471
506
 
472
- return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
507
+ return super.startTestSpan(name, suite, testSuiteSpan, extraTags)
473
508
  }
474
509
  }
475
510
 
@@ -41,8 +41,11 @@ function getFormattedJestTestParameters (testParameters) {
41
41
  return formattedParameters
42
42
  }
43
43
 
44
+ // Support for `@fast-check/jest`: this library modifies the test name to include the seed
45
+ // A test name that keeps changing breaks some Test Optimization's features.
46
+ const SEED_SUFFIX_RE = /\s*\(with seed=-?\d+\)\s*$/i
44
47
  // https://github.com/facebook/jest/blob/3e38157ad5f23fb7d24669d24fae8ded06a7ab75/packages/jest-circus/src/utils.ts#L396
45
- function getJestTestName (test) {
48
+ function getJestTestName (test, shouldStripSeed = false) {
46
49
  const titles = []
47
50
  let parent = test
48
51
  do {
@@ -50,7 +53,12 @@ function getJestTestName (test) {
50
53
  } while ((parent = parent.parent))
51
54
 
52
55
  titles.shift() // remove TOP_DESCRIBE_BLOCK_NAME
53
- return titles.join(' ')
56
+
57
+ const testName = titles.join(' ')
58
+ if (shouldStripSeed) {
59
+ return testName.replace(SEED_SUFFIX_RE, '')
60
+ }
61
+ return testName
54
62
  }
55
63
 
56
64
  function isMarkedAsUnskippable (test) {
@@ -169,13 +169,13 @@ class MochaPlugin extends CiPlugin {
169
169
  return ctx.currentStore
170
170
  })
171
171
 
172
- this.addSub('ci:mocha:test:is-modified', ({ modifiedTests, file, onDone }) => {
172
+ this.addSub('ci:mocha:test:is-modified', ({ modifiedFiles, file, onDone }) => {
173
173
  const testPath = getTestSuitePath(file, this.repositoryRoot)
174
174
  const isModified = isModifiedTest(
175
175
  testPath,
176
176
  null,
177
177
  null,
178
- modifiedTests,
178
+ modifiedFiles,
179
179
  this.constructor.id
180
180
  )
181
181
 
@@ -60,11 +60,11 @@ class PlaywrightPlugin extends CiPlugin {
60
60
 
61
61
  this.addSub('ci:playwright:test:is-modified', ({
62
62
  filePath,
63
- modifiedTests,
63
+ modifiedFiles,
64
64
  onDone
65
65
  }) => {
66
66
  const testSuite = getTestSuitePath(filePath, this.repositoryRoot)
67
- const isModified = isModifiedTest(testSuite, 0, 0, modifiedTests, this.constructor.id)
67
+ const isModified = isModifiedTest(testSuite, 0, 0, modifiedFiles, this.constructor.id)
68
68
  onDone({ isModified })
69
69
  })
70
70
 
@@ -90,9 +90,9 @@ class VitestPlugin extends CiPlugin {
90
90
  onDone(isQuarantined)
91
91
  })
92
92
 
93
- this.addSub('ci:vitest:test:is-modified', ({ modifiedTests, testSuiteAbsolutePath, onDone }) => {
93
+ this.addSub('ci:vitest:test:is-modified', ({ modifiedFiles, testSuiteAbsolutePath, onDone }) => {
94
94
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
95
- const isModified = isModifiedTest(testSuite, 0, 0, modifiedTests, this.constructor.id)
95
+ const isModified = isModifiedTest(testSuite, 0, 0, modifiedFiles, this.constructor.id)
96
96
 
97
97
  onDone(isModified)
98
98
  })
@@ -58,7 +58,7 @@ class WSClosePlugin extends TracingPlugin {
58
58
  }
59
59
 
60
60
  end (ctx) {
61
- if (!Object.hasOwn(ctx, 'result')) return
61
+ if (!Object.hasOwn(ctx, 'result') || !ctx.span) return
62
62
 
63
63
  if (ctx.socket.spanContext) ctx.span.addLink({ context: ctx.socket.spanContext })
64
64
 
@@ -48,7 +48,7 @@ class WSProducerPlugin extends TracingPlugin {
48
48
  }
49
49
 
50
50
  end (ctx) {
51
- if (!Object.hasOwn(ctx, 'result')) return
51
+ if (!Object.hasOwn(ctx, 'result') || !ctx.span) return
52
52
 
53
53
  if (ctx.socket.spanContext) {
54
54
  ctx.span.addLink({
@@ -58,7 +58,7 @@ class WSReceiverPlugin extends TracingPlugin {
58
58
  }
59
59
 
60
60
  end (ctx) {
61
- if (!Object.hasOwn(ctx, 'result')) return
61
+ if (!Object.hasOwn(ctx, 'result') || !ctx.span) return
62
62
 
63
63
  if (ctx.socket.spanContext) {
64
64
  ctx.span.addLink({
@@ -21,8 +21,10 @@ class WSServerPlugin extends TracingPlugin {
21
21
 
22
22
  const protocol = `${getRequestProtocol(req)}:`
23
23
  const host = options.headers.host
24
- const path = req.url
25
- const uri = `${protocol}//${host}${path}`
24
+ const url = req.url
25
+ const indexOfParam = url.indexOf('?')
26
+ const route = indexOfParam === -1 ? url : url.slice(0, indexOfParam)
27
+ const uri = `${protocol}//${host}${route}`
26
28
 
27
29
  ctx.args = { options }
28
30
 
@@ -34,7 +36,7 @@ class WSServerPlugin extends TracingPlugin {
34
36
  'http.upgraded': 'websocket',
35
37
  'http.method': options.method,
36
38
  'http.url': uri,
37
- 'resource.name': `${options.method} ${path}`,
39
+ 'resource.name': `${options.method} ${route}`,
38
40
  'span.kind': 'server'
39
41
 
40
42
  }
@@ -43,6 +43,7 @@ const { isInServerlessEnvironment } = require('../serverless')
43
43
 
44
44
  const responseAnalyzedSet = new WeakSet()
45
45
  const storedResponseHeaders = new WeakMap()
46
+ const storedBodies = new WeakMap()
46
47
 
47
48
  let isEnabled = false
48
49
  let config
@@ -114,6 +115,11 @@ function onRequestBodyParsed ({ req, res, body, abortController }) {
114
115
  const rootSpan = web.root(req)
115
116
  if (!rootSpan) return
116
117
 
118
+ if (!req.body) {
119
+ // do not store body if it is in req.body
120
+ storedBodies.set(req, body)
121
+ }
122
+
117
123
  const results = waf.run({
118
124
  persistent: {
119
125
  [addresses.HTTP_INCOMING_BODY]: body
@@ -200,11 +206,13 @@ function incomingHttpEndTranslator ({ req, res }) {
200
206
 
201
207
  const storedHeaders = storedResponseHeaders.get(req) || {}
202
208
 
203
- Reporter.finishRequest(req, res, storedHeaders)
209
+ const body = req.body || storedBodies.get(req)
210
+ Reporter.finishRequest(req, res, storedHeaders, body)
204
211
 
205
212
  if (storedHeaders) {
206
213
  storedResponseHeaders.delete(req)
207
214
  }
215
+ storedBodies.delete(req)
208
216
  }
209
217
 
210
218
  function onPassportVerify ({ framework, login, user, success, abortController }) {