dd-trace 5.70.0 → 5.72.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 (72) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/index.d.ts +110 -1
  3. package/initialize.mjs +7 -1
  4. package/package.json +21 -2
  5. package/packages/datadog-instrumentations/src/anthropic.js +115 -0
  6. package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
  7. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +7 -7
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/jest.js +29 -36
  11. package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +1 -1
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  14. package/packages/datadog-instrumentations/src/pg.js +1 -1
  15. package/packages/datadog-instrumentations/src/playwright.js +5 -5
  16. package/packages/datadog-instrumentations/src/vitest.js +8 -8
  17. package/packages/datadog-plugin-anthropic/src/index.js +17 -0
  18. package/packages/datadog-plugin-anthropic/src/tracing.js +30 -0
  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 +37 -0
  23. package/packages/datadog-plugin-cucumber/src/index.js +3 -3
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
  25. package/packages/datadog-plugin-jest/src/util.js +10 -2
  26. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  27. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  28. package/packages/datadog-plugin-vitest/src/index.js +2 -2
  29. package/packages/datadog-plugin-ws/src/server.js +5 -3
  30. package/packages/dd-trace/src/appsec/reporter.js +70 -21
  31. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  32. package/packages/dd-trace/src/config.js +110 -26
  33. package/packages/dd-trace/src/config_defaults.js +14 -0
  34. package/packages/dd-trace/src/git_properties.js +90 -5
  35. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +282 -0
  36. package/packages/dd-trace/src/llmobs/tagger.js +35 -0
  37. package/packages/dd-trace/src/noop/proxy.js +3 -0
  38. package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
  39. package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
  40. package/packages/dd-trace/src/openfeature/index.js +77 -0
  41. package/packages/dd-trace/src/openfeature/noop.js +101 -0
  42. package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
  43. package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
  44. package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
  45. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
  46. package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
  47. package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
  48. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
  49. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +173 -0
  50. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +367 -0
  51. package/packages/dd-trace/src/opentelemetry/protos/common.proto +116 -0
  52. package/packages/dd-trace/src/opentelemetry/protos/logs.proto +226 -0
  53. package/packages/dd-trace/src/opentelemetry/protos/logs_service.proto +78 -0
  54. package/packages/dd-trace/src/opentelemetry/protos/protobuf_loader.js +48 -0
  55. package/packages/dd-trace/src/opentelemetry/protos/resource.proto +45 -0
  56. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
  57. package/packages/dd-trace/src/plugins/index.js +2 -0
  58. package/packages/dd-trace/src/plugins/util/test.js +6 -5
  59. package/packages/dd-trace/src/profiling/config.js +21 -1
  60. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
  61. package/packages/dd-trace/src/profiling/profiler.js +44 -22
  62. package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
  63. package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
  64. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
  65. package/packages/dd-trace/src/proxy.js +22 -1
  66. package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
  67. package/packages/dd-trace/src/remote_config/index.js +3 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
  69. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  70. package/packages/dd-trace/src/supported-configurations.json +18 -0
  71. package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
  72. package/register.js +9 -1
@@ -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 {
@@ -25,6 +26,8 @@ class AzureFunctionsPlugin extends TracingPlugin {
25
26
  bindStart (ctx) {
26
27
  const childOf = extractTraceContext(this._tracer, ctx)
27
28
  const meta = getMetaForTrigger(ctx)
29
+ const triggerType = triggerMap[ctx.methodName]
30
+ const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
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(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
@@ -101,6 +118,26 @@ function extractTraceContext (tracer, ctx) {
101
118
  return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
102
119
  case 'ServiceBus':
103
120
  return tracer.extract('text_map', ctx.invocationContext.triggerMetadata.applicationProperties)
121
+ default:
122
+ null
123
+ }
124
+ }
125
+
126
+ function setSpanLinks (tracer, span, ctx) {
127
+ const cardinality = ctx.invocationContext.options.trigger.cardinality
128
+ const triggerMetadata = ctx.invocationContext.triggerMetadata
129
+ if (cardinality === 'many' && triggerMetadata.propertiesArray.length > 0) {
130
+ triggerMetadata.propertiesArray.forEach(event => {
131
+ // Check for possible empty event when span links are disabled
132
+ if (Object.keys(event).length > 0) {
133
+ span.addLink(tracer.extract('text_map', event))
134
+ }
135
+ })
136
+ } else if (cardinality === 'one') {
137
+ const spanContext = tracer.extract('text_map', triggerMetadata.properties)
138
+ if (spanContext) {
139
+ span.addLink(spanContext)
140
+ }
104
141
  }
105
142
  }
106
143
 
@@ -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
@@ -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
  })
@@ -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
  }
@@ -38,14 +38,20 @@ const config = {
38
38
 
39
39
  const metricsQueue = new Map()
40
40
 
41
+ const extendedDataCollectionRequest = new WeakMap()
42
+
41
43
  // following header lists are ordered in the same way the spec orders them, it doesn't matter but it's easier to compare
42
44
  const contentHeaderList = [
43
45
  'content-length',
44
- 'content-type',
45
46
  'content-encoding',
46
47
  'content-language'
47
48
  ]
48
49
 
50
+ const responseHeaderList = [
51
+ ...contentHeaderList,
52
+ 'content-type'
53
+ ]
54
+
49
55
  const identificationHeaders = [
50
56
  'x-amzn-trace-id',
51
57
  'cloudfront-viewer-ja3-fingerprint',
@@ -75,15 +81,27 @@ const requestHeadersList = [
75
81
  ...identificationHeaders
76
82
  ]
77
83
 
84
+ const redactedHeadersList = [
85
+ 'authorization',
86
+ 'proxy-authorization',
87
+ 'www-authenticate',
88
+ 'proxy-authenticate',
89
+ 'authentication-info',
90
+ 'proxy-authentication-info',
91
+ 'cookie',
92
+ 'set-cookie'
93
+ ]
94
+
78
95
  // these request headers are always collected - it breaks the expected spec orders
79
96
  const REQUEST_HEADERS_MAP = mapHeaderAndTags(requestHeadersList, REQUEST_HEADER_TAG_PREFIX)
80
97
 
81
98
  const EVENT_HEADERS_MAP = mapHeaderAndTags(eventHeadersList, REQUEST_HEADER_TAG_PREFIX)
82
99
 
83
- const RESPONSE_HEADERS_MAP = mapHeaderAndTags(contentHeaderList, RESPONSE_HEADER_TAG_PREFIX)
100
+ const RESPONSE_HEADERS_MAP = mapHeaderAndTags(responseHeaderList, RESPONSE_HEADER_TAG_PREFIX)
84
101
 
85
102
  const NON_EXTENDED_REQUEST_HEADERS = new Set([...requestHeadersList, ...eventHeadersList])
86
- const NON_EXTENDED_RESPONSE_HEADERS = new Set(contentHeaderList)
103
+ const NON_EXTENDED_RESPONSE_HEADERS = new Set(responseHeaderList)
104
+ const REDACTED_HEADERS = new Set(redactedHeadersList)
87
105
 
88
106
  function init (_config) {
89
107
  config.headersExtendedCollectionEnabled = _config.extendedHeadersCollection.enabled
@@ -132,7 +150,9 @@ function filterExtendedHeaders (headers, excludedHeaderNames, tagPrefix, limit =
132
150
  for (const [headerName, headerValue] of Object.entries(headers)) {
133
151
  if (counter >= limit) break
134
152
  if (!excludedHeaderNames.has(headerName)) {
135
- result[getHeaderTag(tagPrefix, headerName)] = String(headerValue)
153
+ result[getHeaderTag(tagPrefix, headerName)] = REDACTED_HEADERS.has(headerName)
154
+ ? '<redacted>'
155
+ : String(headerValue)
136
156
  counter++
137
157
  }
138
158
  }
@@ -140,7 +160,7 @@ function filterExtendedHeaders (headers, excludedHeaderNames, tagPrefix, limit =
140
160
  return result
141
161
  }
142
162
 
143
- function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedResponseHeaders = {}) {
163
+ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedResponseHeaders = {}, extendedDataCollection) {
144
164
  // Mandatory
145
165
  const mandatoryCollectedHeaders = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
146
166
 
@@ -154,7 +174,8 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
154
174
  const requestEventCollectedHeaders = filterHeaders(req.headers, EVENT_HEADERS_MAP)
155
175
  const responseEventCollectedHeaders = filterHeaders(responseHeaders, RESPONSE_HEADERS_MAP)
156
176
 
157
- if (!config.headersExtendedCollectionEnabled || config.headersRedaction) {
177
+ // TODO headersExtendedCollectionEnabled and headersRedaction properties are deprecated to delete in a major
178
+ if ((!config.headersExtendedCollectionEnabled || config.headersRedaction) && !extendedDataCollection) {
158
179
  // Standard collection
159
180
  return Object.assign(
160
181
  mandatoryCollectedHeaders,
@@ -163,12 +184,15 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
163
184
  )
164
185
  }
165
186
 
187
+ // TODO config.maxHeadersCollected is deprecated to delete in a major
188
+ const maxHeadersCollected = extendedDataCollection?.max_collected_headers ?? config.maxHeadersCollected
189
+
166
190
  // Extended collection
167
- const requestExtendedHeadersAvailableCount =
168
- config.maxHeadersCollected -
169
- Object.keys(mandatoryCollectedHeaders).length -
191
+ const collectedHeadersCount = Object.keys(mandatoryCollectedHeaders).length +
170
192
  Object.keys(requestEventCollectedHeaders).length
171
193
 
194
+ const requestExtendedHeadersAvailableCount = maxHeadersCollected - collectedHeadersCount
195
+
172
196
  const requestEventExtendedCollectedHeaders =
173
197
  filterExtendedHeaders(
174
198
  req.headers,
@@ -178,7 +202,7 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
178
202
  )
179
203
 
180
204
  const responseExtendedHeadersAvailableCount =
181
- config.maxHeadersCollected -
205
+ maxHeadersCollected -
182
206
  Object.keys(responseEventCollectedHeaders).length
183
207
 
184
208
  const responseEventExtendedCollectedHeaders =
@@ -199,15 +223,15 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
199
223
 
200
224
  // Check discarded headers
201
225
  const requestHeadersCount = Object.keys(req.headers).length
202
- if (requestHeadersCount > config.maxHeadersCollected) {
226
+ if (requestHeadersCount > maxHeadersCollected) {
203
227
  headersTags['_dd.appsec.request.header_collection.discarded'] =
204
- requestHeadersCount - config.maxHeadersCollected
228
+ requestHeadersCount - maxHeadersCollected
205
229
  }
206
230
 
207
231
  const responseHeadersCount = Object.keys(responseHeaders).length
208
- if (responseHeadersCount > config.maxHeadersCollected) {
232
+ if (responseHeadersCount > maxHeadersCollected) {
209
233
  headersTags['_dd.appsec.response.header_collection.discarded'] =
210
- responseHeadersCount - config.maxHeadersCollected
234
+ responseHeadersCount - maxHeadersCollected
211
235
  }
212
236
 
213
237
  return headersTags
@@ -307,7 +331,7 @@ function reportTruncationMetrics (rootSpan, metrics) {
307
331
  }
308
332
  }
309
333
 
310
- function reportAttack (attackData) {
334
+ function reportAttack ({ events: attackData, actions }) {
311
335
  const store = storage('legacy').getStore()
312
336
  const req = store?.req
313
337
  const rootSpan = web.root(req)
@@ -338,8 +362,14 @@ function reportAttack (attackData) {
338
362
 
339
363
  rootSpan.addTags(newTags)
340
364
 
365
+ // TODO this should be deleted in a major
341
366
  if (config.raspBodyCollection && isRaspAttack(attackData)) {
342
- reportRequestBody(rootSpan, req.body)
367
+ reportRequestBody(rootSpan, req.body, true)
368
+ }
369
+
370
+ const extendedDataCollection = actions?.extended_data_collection
371
+ if (extendedDataCollection) {
372
+ extendedDataCollectionRequest.set(req, extendedDataCollection)
343
373
  }
344
374
  }
345
375
 
@@ -398,18 +428,29 @@ function truncateRequestBody (target, depth = 0) {
398
428
  }
399
429
  }
400
430
 
401
- function reportRequestBody (rootSpan, requestBody) {
402
- if (!requestBody) return
431
+ function reportRequestBody (rootSpan, requestBody, comesFromRaspAction = false) {
432
+ if (!requestBody || Object.keys(requestBody).length === 0) return
403
433
 
404
434
  if (!rootSpan.meta_struct) {
405
435
  rootSpan.meta_struct = {}
406
436
  }
407
437
 
408
- if (!rootSpan.meta_struct['http.request.body']) {
438
+ if (rootSpan.meta_struct['http.request.body']) {
439
+ // If the rasp.exceed metric exists, set also the same for the new tag
440
+ const currentTags = rootSpan.context()._tags
441
+ const sizeExceedTagValue = currentTags['_dd.appsec.rasp.request_body_size.exceeded']
442
+
443
+ if (sizeExceedTagValue) {
444
+ rootSpan.setTag('_dd.appsec.request_body_size.exceeded', sizeExceedTagValue)
445
+ }
446
+ } else {
409
447
  const { truncated, value } = truncateRequestBody(requestBody)
410
448
  rootSpan.meta_struct['http.request.body'] = value
411
449
  if (truncated) {
412
- rootSpan.setTag('_dd.appsec.rasp.request_body_size.exceeded', 'true')
450
+ const sizeExceedTagKey = comesFromRaspAction
451
+ ? '_dd.appsec.rasp.request_body_size.exceeded' // TODO old metric to delete in a major
452
+ : '_dd.appsec.request_body_size.exceeded'
453
+ rootSpan.setTag(sizeExceedTagKey, 'true')
413
454
  }
414
455
  }
415
456
  }
@@ -496,7 +537,15 @@ function finishRequest (req, res, storedResponseHeaders) {
496
537
 
497
538
  const tags = rootSpan.context()._tags
498
539
 
499
- const newTags = getCollectedHeaders(req, res, shouldCollectEventHeaders(tags), storedResponseHeaders)
540
+ const extendedDataCollection = extendedDataCollectionRequest.get(req)
541
+ const newTags = getCollectedHeaders(
542
+ req, res, shouldCollectEventHeaders(tags), storedResponseHeaders, extendedDataCollection
543
+ )
544
+
545
+ if (extendedDataCollection) {
546
+ // TODO add support for fastify, req.body is not available in fastify
547
+ reportRequestBody(rootSpan, req.body)
548
+ }
500
549
 
501
550
  if (tags['appsec.event'] === 'true' && typeof req.route?.path === 'string') {
502
551
  newTags['http.endpoint'] = req.route.path
@@ -141,7 +141,7 @@ class WAFContextWrapper {
141
141
  metrics.wafTimeout = result.timeout
142
142
 
143
143
  if (ruleTriggered) {
144
- Reporter.reportAttack(result.events)
144
+ Reporter.reportAttack(result)
145
145
  }
146
146
 
147
147
  Reporter.reportAttributes(result.attributes)