dd-trace 3.42.0 → 3.43.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/index.d.ts +5 -0
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/http/client.js +2 -14
- package/packages/datadog-instrumentations/src/next.js +15 -5
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +32 -6
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/index.js +29 -40
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +25 -13
- package/packages/dd-trace/src/config.js +5 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
- package/packages/dd-trace/src/profiling/profiler.js +7 -6
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -12
- package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
- package/packages/dd-trace/src/proxy.js +4 -0
|
@@ -18,30 +18,42 @@ class WAFContextWrapper {
|
|
|
18
18
|
this.addressesToSkip = new Set()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
run (
|
|
21
|
+
run ({ persistent, ephemeral }) {
|
|
22
|
+
const payload = {}
|
|
23
|
+
let payloadHasData = false
|
|
22
24
|
const inputs = {}
|
|
23
|
-
let someInputAdded = false
|
|
24
25
|
const newAddressesToSkip = new Set(this.addressesToSkip)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
if (persistent && typeof persistent === 'object') {
|
|
28
|
+
// TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext
|
|
29
|
+
for (const key of Object.keys(persistent)) {
|
|
30
|
+
// TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
|
|
31
|
+
// future versions when the actual addresses are included in the 'loaded' section inside diagnostics.
|
|
32
|
+
if (!this.addressesToSkip.has(key)) {
|
|
33
|
+
inputs[key] = persistent[key]
|
|
34
|
+
if (preventDuplicateAddresses.has(key)) {
|
|
35
|
+
newAddressesToSkip.add(key)
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
|
-
someInputAdded = true
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
if (
|
|
41
|
+
if (Object.keys(inputs).length) {
|
|
42
|
+
payload['persistent'] = inputs
|
|
43
|
+
payloadHasData = true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (ephemeral && Object.keys(ephemeral).length) {
|
|
47
|
+
payload['ephemeral'] = ephemeral
|
|
48
|
+
payloadHasData = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!payloadHasData) return
|
|
40
52
|
|
|
41
53
|
try {
|
|
42
54
|
const start = process.hrtime.bigint()
|
|
43
55
|
|
|
44
|
-
const result = this.ddwafContext.run(
|
|
56
|
+
const result = this.ddwafContext.run(payload, this.wafTimeout)
|
|
45
57
|
|
|
46
58
|
const end = process.hrtime.bigint()
|
|
47
59
|
|
|
@@ -435,6 +435,10 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
435
435
|
maybeFile(appsec.blockedTemplateJson),
|
|
436
436
|
maybeFile(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON)
|
|
437
437
|
)
|
|
438
|
+
const DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON = coalesce(
|
|
439
|
+
maybeFile(appsec.blockedTemplateGraphql),
|
|
440
|
+
maybeFile(process.env.DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON)
|
|
441
|
+
)
|
|
438
442
|
const DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING = coalesce(
|
|
439
443
|
appsec.eventTracking && appsec.eventTracking.mode,
|
|
440
444
|
process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING,
|
|
@@ -644,6 +648,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
644
648
|
obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
|
|
645
649
|
blockedTemplateHtml: DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML,
|
|
646
650
|
blockedTemplateJson: DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON,
|
|
651
|
+
blockedTemplateGraphql: DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON,
|
|
647
652
|
eventTracking: {
|
|
648
653
|
enabled: ['extended', 'safe'].includes(DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING),
|
|
649
654
|
mode: DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING
|
|
@@ -27,10 +27,11 @@ function removeEmptyValues (tags) {
|
|
|
27
27
|
}, {})
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
// The regex is
|
|
30
|
+
// The regex is inspired by
|
|
31
31
|
// https://github.com/jonschlinkert/is-git-url/blob/396965ffabf2f46656c8af4c47bef1d69f09292e/index.js#L9C15-L9C87
|
|
32
|
+
// The `.git` suffix is optional in this version
|
|
32
33
|
function validateGitRepositoryUrl (repoUrl) {
|
|
33
|
-
return /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(
|
|
34
|
+
return /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\/?|#[-\d\w._]+?)$/.test(repoUrl)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function validateGitCommitSha (gitCommitSha) {
|
|
@@ -61,6 +61,7 @@ class Profiler extends EventEmitter {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
try {
|
|
64
|
+
const start = new Date()
|
|
64
65
|
for (const profiler of config.profilers) {
|
|
65
66
|
// TODO: move this out of Profiler when restoring sourcemap support
|
|
66
67
|
profiler.start({
|
|
@@ -70,7 +71,7 @@ class Profiler extends EventEmitter {
|
|
|
70
71
|
this._logger.debug(`Started ${profiler.type} profiler`)
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
this._capture(this._timeoutInterval)
|
|
74
|
+
this._capture(this._timeoutInterval, start)
|
|
74
75
|
return true
|
|
75
76
|
} catch (e) {
|
|
76
77
|
this._logger.error(e)
|
|
@@ -116,9 +117,9 @@ class Profiler extends EventEmitter {
|
|
|
116
117
|
return this
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
_capture (timeout) {
|
|
120
|
+
_capture (timeout, start) {
|
|
120
121
|
if (!this._enabled) return
|
|
121
|
-
this._lastStart =
|
|
122
|
+
this._lastStart = start
|
|
122
123
|
if (!this._timer || timeout !== this._timeoutInterval) {
|
|
123
124
|
this._timer = setTimeout(() => this._collect(snapshotKinds.PERIODIC), timeout)
|
|
124
125
|
this._timer.unref()
|
|
@@ -138,7 +139,7 @@ class Profiler extends EventEmitter {
|
|
|
138
139
|
try {
|
|
139
140
|
// collect profiles synchronously so that profilers can be safely stopped asynchronously
|
|
140
141
|
for (const profiler of this._config.profilers) {
|
|
141
|
-
const profile = profiler.profile()
|
|
142
|
+
const profile = profiler.profile(start, end)
|
|
142
143
|
if (!profile) continue
|
|
143
144
|
profiles.push({ profiler, profile })
|
|
144
145
|
}
|
|
@@ -154,7 +155,7 @@ class Profiler extends EventEmitter {
|
|
|
154
155
|
})
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
this._capture(this._timeoutInterval)
|
|
158
|
+
this._capture(this._timeoutInterval, end)
|
|
158
159
|
await this._submit(encodedProfiles, start, end, snapshotKind)
|
|
159
160
|
this._logger.debug('Submitted profiles')
|
|
160
161
|
} catch (err) {
|
|
@@ -201,7 +202,7 @@ class ServerlessProfiler extends Profiler {
|
|
|
201
202
|
await super._collect(snapshotKind)
|
|
202
203
|
} else {
|
|
203
204
|
this._profiledIntervals += 1
|
|
204
|
-
this._capture(this._timeoutInterval)
|
|
205
|
+
this._capture(this._timeoutInterval, new Date())
|
|
205
206
|
// Don't submit profile until 65 (flushAfterIntervals) intervals have elapsed
|
|
206
207
|
}
|
|
207
208
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
|
|
2
|
-
const {
|
|
2
|
+
const { END_TIMESTAMP_LABEL } = require('./shared')
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
5
|
const pprof = require('@datadog/pprof/')
|
|
@@ -174,7 +174,7 @@ class EventsProfiler {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
profile () {
|
|
177
|
+
profile (startDate, endDate) {
|
|
178
178
|
if (this.entries.length === 0) {
|
|
179
179
|
// No events in the period; don't produce a profile
|
|
180
180
|
return null
|
|
@@ -202,12 +202,11 @@ class EventsProfiler {
|
|
|
202
202
|
decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
|
|
203
203
|
decorators[eventType] = decorator
|
|
204
204
|
}
|
|
205
|
-
const timestampLabelKey = stringTable.dedup(
|
|
205
|
+
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
|
|
206
206
|
|
|
207
|
-
let durationFrom = Number.POSITIVE_INFINITY
|
|
208
|
-
let durationTo = 0
|
|
209
207
|
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
210
|
-
|
|
208
|
+
const lateEntries = []
|
|
209
|
+
const perfEndDate = endDate.getTime() - performance.timeOrigin
|
|
211
210
|
const samples = this.entries.map((item) => {
|
|
212
211
|
const decorator = decorators[item.entryType]
|
|
213
212
|
if (!decorator) {
|
|
@@ -216,9 +215,15 @@ class EventsProfiler {
|
|
|
216
215
|
return null
|
|
217
216
|
}
|
|
218
217
|
const { startTime, duration } = item
|
|
218
|
+
if (startTime >= perfEndDate) {
|
|
219
|
+
// An event past the current recording end date; save it for the next
|
|
220
|
+
// profile. Not supposed to happen as long as there's no async activity
|
|
221
|
+
// between capture of the endDate value in profiler.js _collect() and
|
|
222
|
+
// here, but better be safe than sorry.
|
|
223
|
+
lateEntries.push(item)
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
219
226
|
const endTime = startTime + duration
|
|
220
|
-
if (durationFrom > startTime) durationFrom = startTime
|
|
221
|
-
if (durationTo < endTime) durationTo = endTime
|
|
222
227
|
const sampleInput = {
|
|
223
228
|
value: [Math.round(duration * MS_TO_NS)],
|
|
224
229
|
locationId,
|
|
@@ -231,7 +236,7 @@ class EventsProfiler {
|
|
|
231
236
|
return new Sample(sampleInput)
|
|
232
237
|
}).filter(v => v)
|
|
233
238
|
|
|
234
|
-
this.entries =
|
|
239
|
+
this.entries = lateEntries
|
|
235
240
|
|
|
236
241
|
const timeValueType = new ValueType({
|
|
237
242
|
type: stringTable.dedup(pprofValueType),
|
|
@@ -240,10 +245,10 @@ class EventsProfiler {
|
|
|
240
245
|
|
|
241
246
|
return new Profile({
|
|
242
247
|
sampleType: [timeValueType],
|
|
243
|
-
timeNanos:
|
|
248
|
+
timeNanos: endDate.getTime() * MS_TO_NS,
|
|
244
249
|
periodType: timeValueType,
|
|
245
|
-
period:
|
|
246
|
-
durationNanos:
|
|
250
|
+
period: 1,
|
|
251
|
+
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
|
|
247
252
|
sample: samples,
|
|
248
253
|
location: locations,
|
|
249
254
|
function: functions,
|
|
@@ -2,8 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
const { isMainThread, threadId } = require('node:worker_threads')
|
|
4
4
|
|
|
5
|
+
const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
|
|
6
|
+
const THREAD_NAME_LABEL = 'thread name'
|
|
7
|
+
const OS_THREAD_ID_LABEL = 'os thread id'
|
|
8
|
+
const THREAD_ID_LABEL = 'thread id'
|
|
9
|
+
const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
|
|
10
|
+
const eventLoopThreadName = `${threadNamePrefix} Event Loop`
|
|
11
|
+
|
|
12
|
+
function getThreadLabels () {
|
|
13
|
+
const pprof = require('@datadog/pprof')
|
|
14
|
+
const nativeThreadId = pprof.getNativeThreadId()
|
|
15
|
+
return {
|
|
16
|
+
[THREAD_NAME_LABEL]: eventLoopThreadName,
|
|
17
|
+
[THREAD_ID_LABEL]: `${threadId}`,
|
|
18
|
+
[OS_THREAD_ID_LABEL]: `${nativeThreadId}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function cacheThreadLabels () {
|
|
23
|
+
let labels
|
|
24
|
+
return () => {
|
|
25
|
+
if (!labels) {
|
|
26
|
+
labels = getThreadLabels()
|
|
27
|
+
}
|
|
28
|
+
return labels
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
5
32
|
module.exports = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
33
|
+
END_TIMESTAMP_LABEL,
|
|
34
|
+
THREAD_NAME_LABEL,
|
|
35
|
+
THREAD_ID_LABEL,
|
|
36
|
+
threadNamePrefix,
|
|
37
|
+
eventLoopThreadName,
|
|
38
|
+
getThreadLabels: cacheThreadLabels()
|
|
9
39
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { oomExportStrategies } = require('../constants')
|
|
4
|
+
const { getThreadLabels } = require('./shared')
|
|
4
5
|
|
|
5
6
|
function strategiesToCallbackMode (strategies, callbackMode) {
|
|
6
7
|
return strategies.includes(oomExportStrategies.ASYNC_CALLBACK) ? callbackMode.Async : 0
|
|
@@ -33,7 +34,7 @@ class NativeSpaceProfiler {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
profile () {
|
|
36
|
-
return this._pprof.heap.profile(undefined, this._mapper)
|
|
37
|
+
return this._pprof.heap.profile(undefined, this._mapper, getThreadLabels)
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
encode (profile) {
|
|
@@ -7,13 +7,12 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
|
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
9
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
10
|
-
const {
|
|
10
|
+
const { END_TIMESTAMP_LABEL, getThreadLabels } = require('./shared')
|
|
11
11
|
|
|
12
12
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
13
13
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
14
14
|
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
15
15
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
16
|
-
const threadName = `${threadNamePrefix} Event Loop`
|
|
17
16
|
|
|
18
17
|
const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
|
|
19
18
|
|
|
@@ -96,12 +95,9 @@ class NativeWallProfiler {
|
|
|
96
95
|
this._enter = this._enter.bind(this)
|
|
97
96
|
this._spanFinished = this._spanFinished.bind(this)
|
|
98
97
|
}
|
|
99
|
-
this._generateLabels = this._generateLabels.bind(this)
|
|
100
|
-
} else {
|
|
101
|
-
// Explicitly assigning, to express the intent that this is meant to be
|
|
102
|
-
// undefined when passed to pprof.time.stop() when not using sample contexts.
|
|
103
|
-
this._generateLabels = undefined
|
|
104
98
|
}
|
|
99
|
+
this._generateLabels = this._generateLabels.bind(this)
|
|
100
|
+
|
|
105
101
|
this._logger = options.logger
|
|
106
102
|
this._started = false
|
|
107
103
|
}
|
|
@@ -239,12 +235,21 @@ class NativeWallProfiler {
|
|
|
239
235
|
return profile
|
|
240
236
|
}
|
|
241
237
|
|
|
242
|
-
_generateLabels (
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
_generateLabels (context) {
|
|
239
|
+
if (context == null) {
|
|
240
|
+
// generateLabels is also called for samples without context.
|
|
241
|
+
// In that case just return thread labels.
|
|
242
|
+
return getThreadLabels()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const labels = { ...getThreadLabels() }
|
|
246
|
+
|
|
247
|
+
const { context: { spanId, rootSpanId, webTags, endpoint }, timestamp } = context
|
|
248
|
+
|
|
249
|
+
if (this._timelineEnabled) {
|
|
245
250
|
// Incoming timestamps are in microseconds, we emit nanos.
|
|
246
|
-
[
|
|
247
|
-
}
|
|
251
|
+
labels[END_TIMESTAMP_LABEL] = timestamp * 1000n
|
|
252
|
+
}
|
|
248
253
|
|
|
249
254
|
if (spanId) {
|
|
250
255
|
labels['span id'] = spanId
|