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.
- package/LICENSE-3rdparty.csv +5 -0
- package/index.d.ts +110 -1
- package/initialize.mjs +7 -1
- package/package.json +21 -2
- package/packages/datadog-instrumentations/src/anthropic.js +115 -0
- package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cucumber.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/jest.js +29 -36
- package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +5 -5
- package/packages/datadog-instrumentations/src/vitest.js +8 -8
- package/packages/datadog-plugin-anthropic/src/index.js +17 -0
- package/packages/datadog-plugin-anthropic/src/tracing.js +30 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
- package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +37 -0
- package/packages/datadog-plugin-cucumber/src/index.js +3 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
- package/packages/datadog-plugin-jest/src/util.js +10 -2
- package/packages/datadog-plugin-mocha/src/index.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +2 -2
- package/packages/datadog-plugin-vitest/src/index.js +2 -2
- package/packages/datadog-plugin-ws/src/server.js +5 -3
- package/packages/dd-trace/src/appsec/reporter.js +70 -21
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/config.js +110 -26
- package/packages/dd-trace/src/config_defaults.js +14 -0
- package/packages/dd-trace/src/git_properties.js +90 -5
- package/packages/dd-trace/src/llmobs/plugins/anthropic.js +282 -0
- package/packages/dd-trace/src/llmobs/tagger.js +35 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
- package/packages/dd-trace/src/openfeature/index.js +77 -0
- package/packages/dd-trace/src/openfeature/noop.js +101 -0
- package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +173 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +367 -0
- package/packages/dd-trace/src/opentelemetry/protos/common.proto +116 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs.proto +226 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/protos/protobuf_loader.js +48 -0
- package/packages/dd-trace/src/opentelemetry/protos/resource.proto +45 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +21 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
- package/packages/dd-trace/src/profiling/profiler.js +44 -22
- package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
- package/packages/dd-trace/src/proxy.js +22 -1
- package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/remote_config/index.js +3 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/supported-configurations.json +18 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
203
|
-
if (
|
|
204
|
-
return
|
|
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
|
-
|
|
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.
|
|
342
|
+
if (!this.modifiedFiles) {
|
|
343
343
|
return false
|
|
344
344
|
}
|
|
345
|
-
const lines = this.
|
|
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.
|
|
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
|
-
|
|
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', ({
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
modifiedFiles,
|
|
64
64
|
onDone
|
|
65
65
|
}) => {
|
|
66
66
|
const testSuite = getTestSuitePath(filePath, this.repositoryRoot)
|
|
67
|
-
const isModified = isModifiedTest(testSuite, 0, 0,
|
|
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', ({
|
|
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,
|
|
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
|
|
25
|
-
const
|
|
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} ${
|
|
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(
|
|
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(
|
|
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)] =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 >
|
|
226
|
+
if (requestHeadersCount > maxHeadersCollected) {
|
|
203
227
|
headersTags['_dd.appsec.request.header_collection.discarded'] =
|
|
204
|
-
requestHeadersCount -
|
|
228
|
+
requestHeadersCount - maxHeadersCollected
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
const responseHeadersCount = Object.keys(responseHeaders).length
|
|
208
|
-
if (responseHeadersCount >
|
|
232
|
+
if (responseHeadersCount > maxHeadersCollected) {
|
|
209
233
|
headersTags['_dd.appsec.response.header_collection.discarded'] =
|
|
210
|
-
responseHeadersCount -
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|