dd-trace 5.30.0 → 5.32.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 +1 -0
- package/README.md +9 -7
- package/package.json +7 -6
- package/packages/datadog-core/src/storage.js +11 -2
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +14 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +70 -36
- package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/vitest.js +107 -59
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +30 -32
- package/packages/datadog-plugin-jest/src/index.js +34 -37
- package/packages/datadog-plugin-langchain/src/index.js +12 -80
- package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
- package/packages/datadog-plugin-mocha/src/index.js +18 -36
- package/packages/datadog-plugin-vitest/src/index.js +20 -34
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
- package/packages/dd-trace/src/config.js +39 -3
- package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
- package/packages/dd-trace/src/crashtracking/noop.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -11
- package/packages/dd-trace/src/debugger/index.js +2 -13
- package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +90 -26
- package/packages/dd-trace/src/llmobs/tagger.js +11 -3
- package/packages/dd-trace/src/llmobs/util.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
- package/packages/dd-trace/src/log/index.js +8 -9
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/noop/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/opentracing/span.js +11 -1
- package/packages/dd-trace/src/opentracing/span_context.js +12 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
- package/packages/dd-trace/src/plugins/util/test.js +42 -12
- package/packages/dd-trace/src/priority_sampler.js +7 -2
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
- package/packages/dd-trace/src/profiling/profiler.js +11 -8
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
- package/packages/dd-trace/src/proxy.js +6 -3
- package/packages/dd-trace/src/scope.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +2 -0
|
@@ -25,19 +25,20 @@ class SensitiveHandler {
|
|
|
25
25
|
|
|
26
26
|
this._sensitiveAnalyzers = new Map()
|
|
27
27
|
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
28
|
-
this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
29
28
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
|
|
30
|
-
this._sensitiveAnalyzers.set(vulnerabilities.
|
|
29
|
+
this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
|
|
30
|
+
return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
|
|
31
|
+
})
|
|
32
|
+
this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
|
|
33
|
+
return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
|
|
34
|
+
})
|
|
31
35
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
|
|
36
|
+
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
|
|
32
37
|
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, sqlSensitiveAnalyzer)
|
|
33
38
|
this._sensitiveAnalyzers.set(vulnerabilities.SSRF, urlSensitiveAnalyzer)
|
|
39
|
+
this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
40
|
+
this._sensitiveAnalyzers.set(vulnerabilities.UNTRUSTED_DESERIALIZATION, taintedRangeBasedSensitiveAnalyzer)
|
|
34
41
|
this._sensitiveAnalyzers.set(vulnerabilities.UNVALIDATED_REDIRECT, urlSensitiveAnalyzer)
|
|
35
|
-
this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
|
|
36
|
-
return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
|
|
37
|
-
})
|
|
38
|
-
this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
|
|
39
|
-
return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
|
|
40
|
-
})
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
isSensibleName (name) {
|
|
@@ -15,6 +15,7 @@ module.exports = {
|
|
|
15
15
|
SSRF: 'SSRF',
|
|
16
16
|
TEMPLATE_INJECTION: 'TEMPLATE_INJECTION',
|
|
17
17
|
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
|
|
18
|
+
UNTRUSTED_DESERIALIZATION: 'UNTRUSTED_DESERIALIZATION',
|
|
18
19
|
WEAK_CIPHER: 'WEAK_CIPHER',
|
|
19
20
|
WEAK_HASH: 'WEAK_HASH',
|
|
20
21
|
WEAK_RANDOMNESS: 'WEAK_RANDOMNESS',
|
|
@@ -9,6 +9,7 @@ const log = require('../../log')
|
|
|
9
9
|
const { getExtraServices } = require('../../service-naming/extra-services')
|
|
10
10
|
const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
|
|
11
11
|
const Scheduler = require('./scheduler')
|
|
12
|
+
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
12
13
|
|
|
13
14
|
const clientId = uuid()
|
|
14
15
|
|
|
@@ -33,6 +34,14 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
33
34
|
port: config.port
|
|
34
35
|
}))
|
|
35
36
|
|
|
37
|
+
const tags = config.repositoryUrl
|
|
38
|
+
? {
|
|
39
|
+
...config.tags,
|
|
40
|
+
[GIT_REPOSITORY_URL]: config.repositoryUrl,
|
|
41
|
+
[GIT_COMMIT_SHA]: config.commitSHA
|
|
42
|
+
}
|
|
43
|
+
: config.tags
|
|
44
|
+
|
|
36
45
|
this._handlers = new Map()
|
|
37
46
|
const appliedConfigs = this.appliedConfigs = new Map()
|
|
38
47
|
|
|
@@ -67,7 +76,8 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
67
76
|
service: config.service,
|
|
68
77
|
env: config.env,
|
|
69
78
|
app_version: config.version,
|
|
70
|
-
extra_services: []
|
|
79
|
+
extra_services: [],
|
|
80
|
+
tags: Object.entries(tags).map((pair) => pair.join(':'))
|
|
71
81
|
},
|
|
72
82
|
capabilities: DEFAULT_CAPABILITY // updated by `updateCapabilities()`
|
|
73
83
|
},
|
|
@@ -19,6 +19,7 @@ class WAFContextWrapper {
|
|
|
19
19
|
this.rulesVersion = rulesVersion
|
|
20
20
|
this.addressesToSkip = new Set()
|
|
21
21
|
this.knownAddresses = knownAddresses
|
|
22
|
+
this.cachedUserIdActions = new Map()
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
run ({ persistent, ephemeral }, raspRule) {
|
|
@@ -27,6 +28,16 @@ class WAFContextWrapper {
|
|
|
27
28
|
return
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
// SPECIAL CASE FOR USER_ID
|
|
32
|
+
// TODO: make this universal
|
|
33
|
+
const userId = persistent?.[addresses.USER_ID] || ephemeral?.[addresses.USER_ID]
|
|
34
|
+
if (userId) {
|
|
35
|
+
const cachedAction = this.cachedUserIdActions.get(userId)
|
|
36
|
+
if (cachedAction) {
|
|
37
|
+
return cachedAction
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
const payload = {}
|
|
31
42
|
let payloadHasData = false
|
|
32
43
|
const newAddressesToSkip = new Set(this.addressesToSkip)
|
|
@@ -79,6 +90,12 @@ class WAFContextWrapper {
|
|
|
79
90
|
|
|
80
91
|
const blockTriggered = !!getBlockingAction(result.actions)
|
|
81
92
|
|
|
93
|
+
// SPECIAL CASE FOR USER_ID
|
|
94
|
+
// TODO: make this universal
|
|
95
|
+
if (userId && ruleTriggered && blockTriggered) {
|
|
96
|
+
this.setUserIdCache(userId, result)
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
Reporter.reportMetrics({
|
|
83
100
|
duration: result.totalRuntime / 1e3,
|
|
84
101
|
durationExt: parseInt(end - start) / 1e3,
|
|
@@ -105,6 +122,26 @@ class WAFContextWrapper {
|
|
|
105
122
|
}
|
|
106
123
|
}
|
|
107
124
|
|
|
125
|
+
setUserIdCache (userId, result) {
|
|
126
|
+
// using old loops for speed
|
|
127
|
+
for (let i = 0; i < result.events.length; i++) {
|
|
128
|
+
const event = result.events[i]
|
|
129
|
+
|
|
130
|
+
for (let j = 0; j < event?.rule_matches?.length; j++) {
|
|
131
|
+
const match = event.rule_matches[j]
|
|
132
|
+
|
|
133
|
+
for (let k = 0; k < match?.parameters?.length; k++) {
|
|
134
|
+
const parameter = match.parameters[k]
|
|
135
|
+
|
|
136
|
+
if (parameter?.address === addresses.USER_ID) {
|
|
137
|
+
this.cachedUserIdActions.set(userId, result.actions)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
108
145
|
dispose () {
|
|
109
146
|
this.ddwafContext.dispose()
|
|
110
147
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { join } = require('path')
|
|
4
|
-
const { Worker } = require('worker_threads')
|
|
4
|
+
const { Worker, threadId: parentThreadId } = require('worker_threads')
|
|
5
5
|
const { randomUUID } = require('crypto')
|
|
6
6
|
const log = require('../../log')
|
|
7
7
|
|
|
8
8
|
const probeIdToResolveBreakpointSet = new Map()
|
|
9
|
-
const
|
|
9
|
+
const probeIdToResolveBreakpointRemove = new Map()
|
|
10
10
|
|
|
11
11
|
class TestVisDynamicInstrumentation {
|
|
12
12
|
constructor () {
|
|
@@ -16,28 +16,34 @@ class TestVisDynamicInstrumentation {
|
|
|
16
16
|
})
|
|
17
17
|
this.breakpointSetChannel = new MessageChannel()
|
|
18
18
|
this.breakpointHitChannel = new MessageChannel()
|
|
19
|
+
this.breakpointRemoveChannel = new MessageChannel()
|
|
20
|
+
this.onHitBreakpointByProbeId = new Map()
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
removeProbe (probeId) {
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
this.breakpointRemoveChannel.port2.postMessage(probeId)
|
|
26
|
+
|
|
27
|
+
probeIdToResolveBreakpointRemove.set(probeId, resolve)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Return 2 elements:
|
|
32
|
+
// 1. Probe ID
|
|
23
33
|
// 2. Promise that's resolved when the breakpoint is set
|
|
24
|
-
|
|
25
|
-
addLineProbe ({ file, line }) {
|
|
26
|
-
const snapshotId = randomUUID()
|
|
34
|
+
addLineProbe ({ file, line }, onHitBreakpoint) {
|
|
27
35
|
const probeId = randomUUID()
|
|
28
36
|
|
|
29
|
-
this.breakpointSetChannel.port2.postMessage(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
this.breakpointSetChannel.port2.postMessage(
|
|
38
|
+
{ id: probeId, file, line }
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
this.onHitBreakpointByProbeId.set(probeId, onHitBreakpoint)
|
|
33
42
|
|
|
34
43
|
return [
|
|
35
|
-
|
|
44
|
+
probeId,
|
|
36
45
|
new Promise(resolve => {
|
|
37
46
|
probeIdToResolveBreakpointSet.set(probeId, resolve)
|
|
38
|
-
}),
|
|
39
|
-
new Promise(resolve => {
|
|
40
|
-
probeIdToResolveBreakpointHit.set(probeId, resolve)
|
|
41
47
|
})
|
|
42
48
|
]
|
|
43
49
|
}
|
|
@@ -46,23 +52,42 @@ class TestVisDynamicInstrumentation {
|
|
|
46
52
|
return this._readyPromise
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
start () {
|
|
55
|
+
start (config) {
|
|
50
56
|
if (this.worker) return
|
|
51
57
|
|
|
52
|
-
const { NODE_OPTIONS, ...envWithoutNodeOptions } = process.env
|
|
53
|
-
|
|
54
58
|
log.debug('Starting Test Visibility - Dynamic Instrumentation client...')
|
|
55
59
|
|
|
60
|
+
const rcChannel = new MessageChannel() // mock channel
|
|
61
|
+
const configChannel = new MessageChannel() // mock channel
|
|
62
|
+
|
|
56
63
|
this.worker = new Worker(
|
|
57
64
|
join(__dirname, 'worker', 'index.js'),
|
|
58
65
|
{
|
|
59
66
|
execArgv: [],
|
|
60
|
-
|
|
67
|
+
// Not passing `NODE_OPTIONS` results in issues with yarn, which relies on NODE_OPTIONS
|
|
68
|
+
// for PnP support, hence why we deviate from the DI pattern here.
|
|
69
|
+
// To avoid infinite initialization loops, we're disabling DI and tracing in the worker.
|
|
70
|
+
env: {
|
|
71
|
+
...process.env,
|
|
72
|
+
DD_TRACE_ENABLED: 0,
|
|
73
|
+
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 0
|
|
74
|
+
},
|
|
61
75
|
workerData: {
|
|
76
|
+
config: config.serialize(),
|
|
77
|
+
parentThreadId,
|
|
78
|
+
rcPort: rcChannel.port1,
|
|
79
|
+
configPort: configChannel.port1,
|
|
62
80
|
breakpointSetChannel: this.breakpointSetChannel.port1,
|
|
63
|
-
breakpointHitChannel: this.breakpointHitChannel.port1
|
|
81
|
+
breakpointHitChannel: this.breakpointHitChannel.port1,
|
|
82
|
+
breakpointRemoveChannel: this.breakpointRemoveChannel.port1
|
|
64
83
|
},
|
|
65
|
-
transferList: [
|
|
84
|
+
transferList: [
|
|
85
|
+
rcChannel.port1,
|
|
86
|
+
configChannel.port1,
|
|
87
|
+
this.breakpointSetChannel.port1,
|
|
88
|
+
this.breakpointHitChannel.port1,
|
|
89
|
+
this.breakpointRemoveChannel.port1
|
|
90
|
+
]
|
|
66
91
|
}
|
|
67
92
|
)
|
|
68
93
|
this.worker.on('online', () => {
|
|
@@ -70,10 +95,18 @@ class TestVisDynamicInstrumentation {
|
|
|
70
95
|
this._onReady()
|
|
71
96
|
})
|
|
72
97
|
|
|
98
|
+
this.worker.on('error', (err) => {
|
|
99
|
+
log.error('Test Visibility - Dynamic Instrumentation worker error', err)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
this.worker.on('messageerror', (err) => {
|
|
103
|
+
log.error('Test Visibility - Dynamic Instrumentation worker messageerror', err)
|
|
104
|
+
})
|
|
105
|
+
|
|
73
106
|
// Allow the parent to exit even if the worker is still running
|
|
74
107
|
this.worker.unref()
|
|
75
108
|
|
|
76
|
-
this.breakpointSetChannel.port2.on('message', (
|
|
109
|
+
this.breakpointSetChannel.port2.on('message', (probeId) => {
|
|
77
110
|
const resolve = probeIdToResolveBreakpointSet.get(probeId)
|
|
78
111
|
if (resolve) {
|
|
79
112
|
resolve()
|
|
@@ -83,15 +116,19 @@ class TestVisDynamicInstrumentation {
|
|
|
83
116
|
|
|
84
117
|
this.breakpointHitChannel.port2.on('message', ({ snapshot }) => {
|
|
85
118
|
const { probe: { id: probeId } } = snapshot
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
probeIdToResolveBreakpointHit.delete(probeId)
|
|
119
|
+
const onHit = this.onHitBreakpointByProbeId.get(probeId)
|
|
120
|
+
if (onHit) {
|
|
121
|
+
onHit({ snapshot })
|
|
90
122
|
}
|
|
91
123
|
}).unref()
|
|
92
124
|
|
|
93
|
-
this.
|
|
94
|
-
|
|
125
|
+
this.breakpointRemoveChannel.port2.on('message', (probeId) => {
|
|
126
|
+
const resolve = probeIdToResolveBreakpointRemove.get(probeId)
|
|
127
|
+
if (resolve) {
|
|
128
|
+
resolve()
|
|
129
|
+
probeIdToResolveBreakpointRemove.delete(probeId)
|
|
130
|
+
}
|
|
131
|
+
}).unref()
|
|
95
132
|
}
|
|
96
133
|
}
|
|
97
134
|
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const sourceMap = require('source-map')
|
|
3
2
|
const path = require('path')
|
|
4
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
workerData: {
|
|
5
|
+
breakpointSetChannel,
|
|
6
|
+
breakpointHitChannel,
|
|
7
|
+
breakpointRemoveChannel
|
|
8
|
+
}
|
|
9
|
+
} = require('worker_threads')
|
|
10
|
+
const { randomUUID } = require('crypto')
|
|
11
|
+
const sourceMap = require('source-map')
|
|
5
12
|
|
|
6
13
|
// TODO: move debugger/devtools_client/session to common place
|
|
7
14
|
const session = require('../../../debugger/devtools_client/session')
|
|
@@ -16,8 +23,8 @@ const log = require('../../../log')
|
|
|
16
23
|
|
|
17
24
|
let sessionStarted = false
|
|
18
25
|
|
|
19
|
-
const breakpointIdToSnapshotId = new Map()
|
|
20
26
|
const breakpointIdToProbe = new Map()
|
|
27
|
+
const probeIdToBreakpointId = new Map()
|
|
21
28
|
|
|
22
29
|
session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint], callFrames } }) => {
|
|
23
30
|
const probe = breakpointIdToProbe.get(hitBreakpoint)
|
|
@@ -32,13 +39,11 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
|
|
|
32
39
|
|
|
33
40
|
await session.post('Debugger.resume')
|
|
34
41
|
|
|
35
|
-
const snapshotId = breakpointIdToSnapshotId.get(hitBreakpoint)
|
|
36
|
-
|
|
37
42
|
const snapshot = {
|
|
38
|
-
id:
|
|
43
|
+
id: randomUUID(),
|
|
39
44
|
timestamp: Date.now(),
|
|
40
45
|
probe: {
|
|
41
|
-
id: probe.
|
|
46
|
+
id: probe.id,
|
|
42
47
|
version: '0',
|
|
43
48
|
location: probe.location
|
|
44
49
|
},
|
|
@@ -56,13 +61,32 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
|
|
|
56
61
|
breakpointHitChannel.postMessage({ snapshot })
|
|
57
62
|
})
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
breakpointRemoveChannel.on('message', async (probeId) => {
|
|
65
|
+
await removeBreakpoint(probeId)
|
|
66
|
+
breakpointRemoveChannel.postMessage(probeId)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
breakpointSetChannel.on('message', async (probe) => {
|
|
70
|
+
await addBreakpoint(probe)
|
|
71
|
+
breakpointSetChannel.postMessage(probe.id)
|
|
63
72
|
})
|
|
64
73
|
|
|
65
|
-
async function
|
|
74
|
+
async function removeBreakpoint (probeId) {
|
|
75
|
+
if (!sessionStarted) {
|
|
76
|
+
// We should not get in this state, but abort if we do, so the code doesn't fail unexpected
|
|
77
|
+
throw Error(`Cannot remove probe ${probeId}: Debugger not started`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const breakpointId = probeIdToBreakpointId.get(probeId)
|
|
81
|
+
if (!breakpointId) {
|
|
82
|
+
throw Error(`Unknown probe id: ${probeId}`)
|
|
83
|
+
}
|
|
84
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId })
|
|
85
|
+
probeIdToBreakpointId.delete(probeId)
|
|
86
|
+
breakpointIdToProbe.delete(breakpointId)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function addBreakpoint (probe) {
|
|
66
90
|
if (!sessionStarted) await start()
|
|
67
91
|
const { file, line } = probe
|
|
68
92
|
|
|
@@ -81,7 +105,7 @@ async function addBreakpoint (snapshotId, probe) {
|
|
|
81
105
|
try {
|
|
82
106
|
lineNumber = await processScriptWithInlineSourceMap({ file, line, sourceMapURL })
|
|
83
107
|
} catch (err) {
|
|
84
|
-
log.error(err)
|
|
108
|
+
log.error('Error processing script with inline source map', err)
|
|
85
109
|
}
|
|
86
110
|
}
|
|
87
111
|
|
|
@@ -93,7 +117,7 @@ async function addBreakpoint (snapshotId, probe) {
|
|
|
93
117
|
})
|
|
94
118
|
|
|
95
119
|
breakpointIdToProbe.set(breakpointId, probe)
|
|
96
|
-
|
|
120
|
+
probeIdToBreakpointId.set(probe.id, breakpointId)
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
function start () {
|
|
@@ -113,14 +137,30 @@ async function processScriptWithInlineSourceMap (params) {
|
|
|
113
137
|
// Parse the source map
|
|
114
138
|
const consumer = await new sourceMap.SourceMapConsumer(decodedSourceMap)
|
|
115
139
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
let generatedPosition
|
|
141
|
+
|
|
142
|
+
// Map to the generated position. We'll attempt with the full file path first, then with the basename.
|
|
143
|
+
// TODO: figure out why sometimes the full path doesn't work
|
|
144
|
+
generatedPosition = consumer.generatedPositionFor({
|
|
145
|
+
source: file,
|
|
119
146
|
line,
|
|
120
147
|
column: 0
|
|
121
148
|
})
|
|
149
|
+
if (generatedPosition.line === null) {
|
|
150
|
+
generatedPosition = consumer.generatedPositionFor({
|
|
151
|
+
source: path.basename(file),
|
|
152
|
+
line,
|
|
153
|
+
column: 0
|
|
154
|
+
})
|
|
155
|
+
}
|
|
122
156
|
|
|
123
157
|
consumer.destroy()
|
|
124
158
|
|
|
159
|
+
// If we can't find the line, just return the original line
|
|
160
|
+
if (generatedPosition.line === null) {
|
|
161
|
+
log.error(`Could not find generated position for ${file}:${line}`)
|
|
162
|
+
return line
|
|
163
|
+
}
|
|
164
|
+
|
|
125
165
|
return generatedPosition.line
|
|
126
166
|
}
|
|
@@ -5,7 +5,8 @@ const {
|
|
|
5
5
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
6
6
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
7
7
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
8
|
-
MOCHA_WORKER_TRACE_PAYLOAD_CODE
|
|
8
|
+
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
9
|
+
JEST_WORKER_LOGS_PAYLOAD_CODE
|
|
9
10
|
} = require('../../../plugins/util/test')
|
|
10
11
|
|
|
11
12
|
function getInterprocessTraceCode () {
|
|
@@ -29,18 +30,27 @@ function getInterprocessCoverageCode () {
|
|
|
29
30
|
return null
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function getInterprocessLogsCode () {
|
|
34
|
+
if (process.env.JEST_WORKER_ID) {
|
|
35
|
+
return JEST_WORKER_LOGS_PAYLOAD_CODE
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* Lightweight exporter whose writers only do simple JSON serialization
|
|
34
|
-
* of trace and
|
|
35
|
-
* Currently used by Jest and
|
|
42
|
+
* of trace, coverage and logs payloads, which they send to the test framework's main process.
|
|
43
|
+
* Currently used by Jest, Cucumber and Mocha workers.
|
|
36
44
|
*/
|
|
37
45
|
class TestWorkerCiVisibilityExporter {
|
|
38
46
|
constructor () {
|
|
39
47
|
const interprocessTraceCode = getInterprocessTraceCode()
|
|
40
48
|
const interprocessCoverageCode = getInterprocessCoverageCode()
|
|
49
|
+
const interprocessLogsCode = getInterprocessLogsCode()
|
|
41
50
|
|
|
42
51
|
this._writer = new Writer(interprocessTraceCode)
|
|
43
52
|
this._coverageWriter = new Writer(interprocessCoverageCode)
|
|
53
|
+
this._logsWriter = new Writer(interprocessLogsCode)
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
export (payload) {
|
|
@@ -51,9 +61,14 @@ class TestWorkerCiVisibilityExporter {
|
|
|
51
61
|
this._coverageWriter.append(formattedCoverage)
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
exportDiLogs (testConfiguration, logMessage) {
|
|
65
|
+
this._logsWriter.append({ testConfiguration, logMessage })
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
flush () {
|
|
55
69
|
this._writer.flush()
|
|
56
70
|
this._coverageWriter.flush()
|
|
71
|
+
this._logsWriter.flush()
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
|
|
@@ -13,15 +13,16 @@ class TestApiManualPlugin extends CiPlugin {
|
|
|
13
13
|
|
|
14
14
|
constructor (...args) {
|
|
15
15
|
super(...args)
|
|
16
|
+
this._isEnvDataCalcualted = false
|
|
16
17
|
this.sourceRoot = process.cwd()
|
|
17
18
|
|
|
18
|
-
this.
|
|
19
|
+
this.unconfiguredAddSub('dd-trace:ci:manual:test:start', ({ testName, testSuite }) => {
|
|
19
20
|
const store = storage.getStore()
|
|
20
21
|
const testSuiteRelative = getTestSuitePath(testSuite, this.sourceRoot)
|
|
21
22
|
const testSpan = this.startTestSpan(testName, testSuiteRelative)
|
|
22
23
|
this.enter(testSpan, store)
|
|
23
24
|
})
|
|
24
|
-
this.
|
|
25
|
+
this.unconfiguredAddSub('dd-trace:ci:manual:test:finish', ({ status, error }) => {
|
|
25
26
|
const store = storage.getStore()
|
|
26
27
|
const testSpan = store && store.span
|
|
27
28
|
if (testSpan) {
|
|
@@ -33,7 +34,7 @@ class TestApiManualPlugin extends CiPlugin {
|
|
|
33
34
|
finishAllTraceSpans(testSpan)
|
|
34
35
|
}
|
|
35
36
|
})
|
|
36
|
-
this.
|
|
37
|
+
this.unconfiguredAddSub('dd-trace:ci:manual:test:addTags', (tags) => {
|
|
37
38
|
const store = storage.getStore()
|
|
38
39
|
const testSpan = store && store.span
|
|
39
40
|
if (testSpan) {
|
|
@@ -41,6 +42,22 @@ class TestApiManualPlugin extends CiPlugin {
|
|
|
41
42
|
}
|
|
42
43
|
})
|
|
43
44
|
}
|
|
45
|
+
|
|
46
|
+
// To lazily calculate env data.
|
|
47
|
+
unconfiguredAddSub (channelName, handler) {
|
|
48
|
+
this.addSub(channelName, (...args) => {
|
|
49
|
+
if (!this._isEnvDataCalcualted) {
|
|
50
|
+
this._isEnvDataCalcualted = true
|
|
51
|
+
this.configure(this._config, true)
|
|
52
|
+
}
|
|
53
|
+
return handler(...args)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
configure (config, shouldGetEnvironmentData) {
|
|
58
|
+
this._config = config
|
|
59
|
+
super.configure(config, shouldGetEnvironmentData)
|
|
60
|
+
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
module.exports = TestApiManualPlugin
|
|
@@ -472,7 +472,9 @@ class Config {
|
|
|
472
472
|
this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
|
|
473
473
|
this._setValue(defaults, 'dogstatsd.port', '8125')
|
|
474
474
|
this._setValue(defaults, 'dsmEnabled', false)
|
|
475
|
-
this._setValue(defaults, '
|
|
475
|
+
this._setValue(defaults, 'dynamicInstrumentation.enabled', false)
|
|
476
|
+
this._setValue(defaults, 'dynamicInstrumentation.redactedIdentifiers', [])
|
|
477
|
+
this._setValue(defaults, 'dynamicInstrumentation.redactionExcludedIdentifiers', [])
|
|
476
478
|
this._setValue(defaults, 'env', undefined)
|
|
477
479
|
this._setValue(defaults, 'experimental.enableGetRumData', false)
|
|
478
480
|
this._setValue(defaults, 'experimental.exporter', undefined)
|
|
@@ -600,6 +602,8 @@ class Config {
|
|
|
600
602
|
DD_DOGSTATSD_HOST,
|
|
601
603
|
DD_DOGSTATSD_PORT,
|
|
602
604
|
DD_DYNAMIC_INSTRUMENTATION_ENABLED,
|
|
605
|
+
DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS,
|
|
606
|
+
DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS,
|
|
603
607
|
DD_ENV,
|
|
604
608
|
DD_EXPERIMENTAL_API_SECURITY_ENABLED,
|
|
605
609
|
DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
|
|
@@ -746,7 +750,13 @@ class Config {
|
|
|
746
750
|
this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST || DD_DOGSTATSD_HOSTNAME)
|
|
747
751
|
this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT)
|
|
748
752
|
this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
|
|
749
|
-
this._setBoolean(env, '
|
|
753
|
+
this._setBoolean(env, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
|
|
754
|
+
this._setArray(env, 'dynamicInstrumentation.redactedIdentifiers', DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS)
|
|
755
|
+
this._setArray(
|
|
756
|
+
env,
|
|
757
|
+
'dynamicInstrumentation.redactionExcludedIdentifiers',
|
|
758
|
+
DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS
|
|
759
|
+
)
|
|
750
760
|
this._setString(env, 'env', DD_ENV || tags.env)
|
|
751
761
|
this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED)
|
|
752
762
|
this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED)
|
|
@@ -926,7 +936,17 @@ class Config {
|
|
|
926
936
|
this._setString(opts, 'dogstatsd.port', options.dogstatsd.port)
|
|
927
937
|
}
|
|
928
938
|
this._setBoolean(opts, 'dsmEnabled', options.dsmEnabled)
|
|
929
|
-
this._setBoolean(opts, '
|
|
939
|
+
this._setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamicInstrumentation?.enabled)
|
|
940
|
+
this._setArray(
|
|
941
|
+
opts,
|
|
942
|
+
'dynamicInstrumentation.redactedIdentifiers',
|
|
943
|
+
options.dynamicInstrumentation?.redactedIdentifiers
|
|
944
|
+
)
|
|
945
|
+
this._setArray(
|
|
946
|
+
opts,
|
|
947
|
+
'dynamicInstrumentation.redactionExcludedIdentifiers',
|
|
948
|
+
options.dynamicInstrumentation?.redactionExcludedIdentifiers
|
|
949
|
+
)
|
|
930
950
|
this._setString(opts, 'env', options.env || tags.env)
|
|
931
951
|
this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
|
|
932
952
|
this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
|
|
@@ -1312,6 +1332,22 @@ class Config {
|
|
|
1312
1332
|
this.sampler.sampleRate = this.sampleRate
|
|
1313
1333
|
updateConfig(changes, this)
|
|
1314
1334
|
}
|
|
1335
|
+
|
|
1336
|
+
// TODO: Refactor the Config class so it never produces any config objects that are incompatible with MessageChannel
|
|
1337
|
+
/**
|
|
1338
|
+
* Serializes the config object so it can be passed over a Worker Thread MessageChannel.
|
|
1339
|
+
* @returns {Object} The serialized config object.
|
|
1340
|
+
*/
|
|
1341
|
+
serialize () {
|
|
1342
|
+
// URL objects cannot be serialized over the MessageChannel, so we need to convert them to strings first
|
|
1343
|
+
if (this.url instanceof URL) {
|
|
1344
|
+
const config = { ...this }
|
|
1345
|
+
config.url = this.url.toString()
|
|
1346
|
+
return config
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return this
|
|
1350
|
+
}
|
|
1315
1351
|
}
|
|
1316
1352
|
|
|
1317
1353
|
function maybeInt (number) {
|
|
@@ -40,6 +40,15 @@ class Crashtracker {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
withProfilerSerializing (f) {
|
|
44
|
+
binding.beginProfilerSerializing()
|
|
45
|
+
try {
|
|
46
|
+
return f()
|
|
47
|
+
} finally {
|
|
48
|
+
binding.endProfilerSerializing()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
// TODO: Send only configured values when defaults are fixed.
|
|
44
53
|
_getConfig (config) {
|
|
45
54
|
const { hostname = '127.0.0.1', port = 8126 } = config
|
|
@@ -15,7 +15,7 @@ function fnv64 (data) {
|
|
|
15
15
|
data = Buffer.from(data, 'utf-8')
|
|
16
16
|
}
|
|
17
17
|
const byteArray = new Uint8Array(data)
|
|
18
|
-
return fnv(byteArray, FNV1_64_INIT, FNV_64_PRIME,
|
|
18
|
+
return fnv(byteArray, FNV1_64_INIT, FNV_64_PRIME, 2n ** 64n)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
module.exports = {
|
|
@@ -23,10 +23,10 @@ async function addBreakpoint (probe) {
|
|
|
23
23
|
delete probe.where
|
|
24
24
|
|
|
25
25
|
// Optimize for fast calculations when probe is hit
|
|
26
|
-
const snapshotsPerSecond = probe.sampling
|
|
26
|
+
const snapshotsPerSecond = probe.sampling?.snapshotsPerSecond ?? (probe.captureSnapshot
|
|
27
27
|
? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
|
|
28
28
|
: MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE)
|
|
29
|
-
probe.
|
|
29
|
+
probe.nsBetweenSampling = BigInt(1 / snapshotsPerSecond * 1e9)
|
|
30
30
|
probe.lastCaptureNs = 0n
|
|
31
31
|
|
|
32
32
|
// TODO: Inbetween `await session.post('Debugger.enable')` and here, the scripts are parsed and cached.
|
|
@@ -5,11 +5,13 @@ const { format } = require('node:url')
|
|
|
5
5
|
const log = require('../../log')
|
|
6
6
|
|
|
7
7
|
const config = module.exports = {
|
|
8
|
+
dynamicInstrumentation: parentConfig.dynamicInstrumentation,
|
|
8
9
|
runtimeId: parentConfig.tags['runtime-id'],
|
|
9
10
|
service: parentConfig.service,
|
|
10
11
|
commitSHA: parentConfig.commitSHA,
|
|
11
12
|
repositoryUrl: parentConfig.repositoryUrl,
|
|
12
|
-
parentThreadId
|
|
13
|
+
parentThreadId,
|
|
14
|
+
maxTotalPayloadSize: 5 * 1024 * 1024 // 5MB
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
updateUrl(parentConfig)
|