dd-trace 4.46.0 → 4.47.1
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 +2 -0
- package/index.d.ts +20 -8
- package/package.json +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +290 -53
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/openai.js +4 -2
- package/packages/datadog-instrumentations/src/pg.js +59 -4
- package/packages/datadog-instrumentations/src/vitest.js +184 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +17 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +27 -18
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
- package/packages/dd-trace/src/config.js +61 -10
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +26 -2
- package/packages/dd-trace/src/profiling/config.js +5 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
- package/packages/dd-trace/src/proxy.js +10 -3
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { workerData: { rcPort } } = require('node:worker_threads')
|
|
4
|
+
const { getScript, probes, breakpoints } = require('./state')
|
|
5
|
+
const session = require('./session')
|
|
6
|
+
const { ackReceived, ackInstalled, ackError } = require('./status')
|
|
7
|
+
const log = require('../../log')
|
|
8
|
+
|
|
9
|
+
let sessionStarted = false
|
|
10
|
+
|
|
11
|
+
// Example log line probe (simplified):
|
|
12
|
+
// {
|
|
13
|
+
// id: '100c9a5c-45ad-49dc-818b-c570d31e11d1',
|
|
14
|
+
// version: 0,
|
|
15
|
+
// type: 'LOG_PROBE',
|
|
16
|
+
// where: { sourceFile: 'index.js', lines: ['25'] }, // only use first array element
|
|
17
|
+
// template: 'Hello World 2',
|
|
18
|
+
// segments: [...],
|
|
19
|
+
// captureSnapshot: true,
|
|
20
|
+
// capture: { maxReferenceDepth: 1 },
|
|
21
|
+
// sampling: { snapshotsPerSecond: 1 },
|
|
22
|
+
// evaluateAt: 'EXIT' // only used for method probes
|
|
23
|
+
// }
|
|
24
|
+
//
|
|
25
|
+
// Example log method probe (simplified):
|
|
26
|
+
// {
|
|
27
|
+
// id: 'd692ee6d-5734-4df7-9d86-e3bc6449cc8c',
|
|
28
|
+
// version: 0,
|
|
29
|
+
// type: 'LOG_PROBE',
|
|
30
|
+
// where: { typeName: 'index.js', methodName: 'handlerA' },
|
|
31
|
+
// template: 'Executed index.js.handlerA, it took {@duration}ms',
|
|
32
|
+
// segments: [...],
|
|
33
|
+
// captureSnapshot: false,
|
|
34
|
+
// capture: { maxReferenceDepth: 3 },
|
|
35
|
+
// sampling: { snapshotsPerSecond: 5000 },
|
|
36
|
+
// evaluateAt: 'EXIT' // only used for method probes
|
|
37
|
+
// }
|
|
38
|
+
rcPort.on('message', async ({ action, conf: probe, ackId }) => {
|
|
39
|
+
try {
|
|
40
|
+
await processMsg(action, probe)
|
|
41
|
+
rcPort.postMessage({ ackId })
|
|
42
|
+
} catch (err) {
|
|
43
|
+
rcPort.postMessage({ ackId, error: err })
|
|
44
|
+
ackError(err, probe)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
rcPort.on('messageerror', (err) => log.error(err))
|
|
48
|
+
|
|
49
|
+
async function start () {
|
|
50
|
+
sessionStarted = true
|
|
51
|
+
return session.post('Debugger.enable') // return instead of await to reduce number of promises created
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function stop () {
|
|
55
|
+
sessionStarted = false
|
|
56
|
+
return session.post('Debugger.disable') // return instead of await to reduce number of promises created
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function processMsg (action, probe) {
|
|
60
|
+
log.debug(`Received request to ${action} ${probe.type} probe (id: ${probe.id}, version: ${probe.version})`)
|
|
61
|
+
|
|
62
|
+
if (action !== 'unapply') ackReceived(probe)
|
|
63
|
+
|
|
64
|
+
if (probe.type !== 'LOG_PROBE') {
|
|
65
|
+
throw new Error(`Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`)
|
|
66
|
+
}
|
|
67
|
+
if (!probe.where.sourceFile && !probe.where.lines) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
// eslint-disable-next-line max-len
|
|
70
|
+
`Unsupported probe insertion point! Only line-based probes are supported (id: ${probe.id}, version: ${probe.version})`
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// This lock is to ensure that we don't get the following race condition:
|
|
75
|
+
//
|
|
76
|
+
// When a breakpoint is being removed and there are no other breakpoints, we disable the debugger by calling
|
|
77
|
+
// `Debugger.disable` to free resources. However, if a new breakpoint is being added around the same time, we might
|
|
78
|
+
// have a race condition where the new breakpoint thinks that the debugger is already enabled because the removal of
|
|
79
|
+
// the other breakpoint hasn't had a chance to call `Debugger.disable` yet. Then once the code that's adding the new
|
|
80
|
+
// breakpoints tries to call `Debugger.setBreakpoint` it fails because in the meantime `Debugger.disable` was called.
|
|
81
|
+
//
|
|
82
|
+
// If the code is ever refactored to not tear down the debugger if there's no active breakpoints, we can safely remove
|
|
83
|
+
// this lock.
|
|
84
|
+
const release = await lock()
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
switch (action) {
|
|
88
|
+
case 'unapply':
|
|
89
|
+
await removeBreakpoint(probe)
|
|
90
|
+
break
|
|
91
|
+
case 'apply':
|
|
92
|
+
await addBreakpoint(probe)
|
|
93
|
+
break
|
|
94
|
+
case 'modify':
|
|
95
|
+
// TODO: Can we modify in place?
|
|
96
|
+
await removeBreakpoint(probe)
|
|
97
|
+
await addBreakpoint(probe)
|
|
98
|
+
break
|
|
99
|
+
default:
|
|
100
|
+
throw new Error(
|
|
101
|
+
// eslint-disable-next-line max-len
|
|
102
|
+
`Cannot process probe ${probe.id} (version: ${probe.version}) - unknown remote configuration action: ${action}`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
release()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function addBreakpoint (probe) {
|
|
111
|
+
if (!sessionStarted) await start()
|
|
112
|
+
|
|
113
|
+
const file = probe.where.sourceFile
|
|
114
|
+
const line = Number(probe.where.lines[0]) // Tracer doesn't support multiple-line breakpoints
|
|
115
|
+
|
|
116
|
+
// Optimize for sending data to /debugger/v1/input endpoint
|
|
117
|
+
probe.location = { file, lines: [line] }
|
|
118
|
+
delete probe.where
|
|
119
|
+
|
|
120
|
+
// TODO: Inbetween `await session.post('Debugger.enable')` and here, the scripts are parsed and cached.
|
|
121
|
+
// Maybe there's a race condition here or maybe we're guraenteed that `await session.post('Debugger.enable')` will
|
|
122
|
+
// not continue untill all scripts have been parsed?
|
|
123
|
+
const script = getScript(file)
|
|
124
|
+
if (!script) throw new Error(`No loaded script found for ${file} (probe: ${probe.id}, version: ${probe.version})`)
|
|
125
|
+
const [path, scriptId] = script
|
|
126
|
+
|
|
127
|
+
log.debug(`Adding breakpoint at ${path}:${line} (probe: ${probe.id}, version: ${probe.version})`)
|
|
128
|
+
|
|
129
|
+
const { breakpointId } = await session.post('Debugger.setBreakpoint', {
|
|
130
|
+
location: {
|
|
131
|
+
scriptId,
|
|
132
|
+
lineNumber: line - 1 // Beware! lineNumber is zero-indexed
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
probes.set(probe.id, breakpointId)
|
|
137
|
+
breakpoints.set(breakpointId, probe)
|
|
138
|
+
|
|
139
|
+
ackInstalled(probe)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function removeBreakpoint ({ id }) {
|
|
143
|
+
if (!sessionStarted) {
|
|
144
|
+
// We should not get in this state, but abort if we do, so the code doesn't fail unexpected
|
|
145
|
+
throw Error(`Cannot remove probe ${id}: Debugger not started`)
|
|
146
|
+
}
|
|
147
|
+
if (!probes.has(id)) {
|
|
148
|
+
throw Error(`Unknown probe id: ${id}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const breakpointId = probes.get(id)
|
|
152
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId })
|
|
153
|
+
probes.delete(id)
|
|
154
|
+
breakpoints.delete(breakpointId)
|
|
155
|
+
|
|
156
|
+
if (breakpoints.size === 0) await stop()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function lock () {
|
|
160
|
+
if (lock.p) await lock.p
|
|
161
|
+
let resolve
|
|
162
|
+
lock.p = new Promise((_resolve) => { resolve = _resolve }).then(() => { lock.p = null })
|
|
163
|
+
return resolve
|
|
164
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const config = require('./config')
|
|
4
|
+
const request = require('../../exporters/common/request')
|
|
5
|
+
|
|
6
|
+
module.exports = send
|
|
7
|
+
|
|
8
|
+
const ddsource = 'dd_debugger'
|
|
9
|
+
const service = config.service
|
|
10
|
+
|
|
11
|
+
function send (message, logger, snapshot, cb) {
|
|
12
|
+
const opts = {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
url: config.url,
|
|
15
|
+
path: '/debugger/v1/input',
|
|
16
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const payload = {
|
|
20
|
+
ddsource,
|
|
21
|
+
service,
|
|
22
|
+
message,
|
|
23
|
+
logger,
|
|
24
|
+
'debugger.snapshot': snapshot
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
request(JSON.stringify(payload), opts, cb)
|
|
28
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const session = require('./session')
|
|
4
|
+
|
|
5
|
+
const scripts = []
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
probes: new Map(),
|
|
9
|
+
breakpoints: new Map(),
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Find the matching script that can be inspected based on a partial path.
|
|
13
|
+
*
|
|
14
|
+
* Algorithm: Find the sortest url that ends in the requested path.
|
|
15
|
+
*
|
|
16
|
+
* Will identify the correct script as long as Node.js doesn't load a module from a `node_modules` folder outside the
|
|
17
|
+
* project root. If so, there's a risk that this path is shorter than the expected path inside the project root.
|
|
18
|
+
* Example of mismatch where path = `index.js`:
|
|
19
|
+
*
|
|
20
|
+
* Expected match: /www/code/my-projects/demo-project1/index.js
|
|
21
|
+
* Actual shorter match: /www/node_modules/dd-trace/index.js
|
|
22
|
+
*
|
|
23
|
+
* To fix this, specify a more unique file path, e.g `demo-project1/index.js` instead of `index.js`
|
|
24
|
+
*
|
|
25
|
+
* @param {string} path
|
|
26
|
+
* @returns {[string, string] | undefined}
|
|
27
|
+
*/
|
|
28
|
+
getScript (path) {
|
|
29
|
+
return scripts
|
|
30
|
+
.filter(([url]) => url.endsWith(path))
|
|
31
|
+
.sort(([a], [b]) => a.length - b.length)[0]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Known params.url protocols:
|
|
36
|
+
// - `node:` - Ignored, as we don't want to instrument Node.js internals
|
|
37
|
+
// - `wasm:` - Ignored, as we don't support instrumenting WebAssembly
|
|
38
|
+
// - `file:` - Regular on-disk file
|
|
39
|
+
// Unknown params.url values:
|
|
40
|
+
// - `structured-stack` - Not sure what this is, but should just be ignored
|
|
41
|
+
// - `` - Not sure what this is, but should just be ignored
|
|
42
|
+
// TODO: Event fired for all files, every time debugger is enabled. So when we disable it, we need to reset the state
|
|
43
|
+
session.on('Debugger.scriptParsed', ({ params }) => {
|
|
44
|
+
if (params.url.startsWith('file:')) {
|
|
45
|
+
scripts.push([params.url, params.scriptId])
|
|
46
|
+
}
|
|
47
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LRUCache = require('lru-cache')
|
|
4
|
+
const config = require('./config')
|
|
5
|
+
const request = require('../../exporters/common/request')
|
|
6
|
+
const FormData = require('../../exporters/common/form-data')
|
|
7
|
+
const log = require('../../log')
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
ackReceived,
|
|
11
|
+
ackInstalled,
|
|
12
|
+
ackEmitting,
|
|
13
|
+
ackError
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ddsource = 'dd_debugger'
|
|
17
|
+
const service = config.service
|
|
18
|
+
const runtimeId = config.runtimeId
|
|
19
|
+
|
|
20
|
+
const cache = new LRUCache({
|
|
21
|
+
ttl: 1000 * 60 * 60, // 1 hour
|
|
22
|
+
// Unfortunate requirement when using LRUCache:
|
|
23
|
+
// It will emit a warning unless `ttlAutopurge`, `max`, or `maxSize` is set when using `ttl`.
|
|
24
|
+
// TODO: Consider alternative as this is NOT performant :(
|
|
25
|
+
ttlAutopurge: true
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const STATUSES = {
|
|
29
|
+
RECEIVED: 'RECEIVED',
|
|
30
|
+
INSTALLED: 'INSTALLED',
|
|
31
|
+
EMITTING: 'EMITTING',
|
|
32
|
+
ERROR: 'ERROR',
|
|
33
|
+
BLOCKED: 'BLOCKED' // TODO: Implement once support for allow list, deny list or max probe limit has been added
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ackReceived ({ id: probeId, version }) {
|
|
37
|
+
onlyUniqueUpdates(
|
|
38
|
+
STATUSES.RECEIVED, probeId, version,
|
|
39
|
+
() => send(statusPayload(probeId, version, STATUSES.RECEIVED))
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ackInstalled ({ id: probeId, version }) {
|
|
44
|
+
onlyUniqueUpdates(
|
|
45
|
+
STATUSES.INSTALLED, probeId, version,
|
|
46
|
+
() => send(statusPayload(probeId, version, STATUSES.INSTALLED))
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ackEmitting ({ id: probeId, version }) {
|
|
51
|
+
onlyUniqueUpdates(
|
|
52
|
+
STATUSES.EMITTING, probeId, version,
|
|
53
|
+
() => send(statusPayload(probeId, version, STATUSES.EMITTING))
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ackError (err, { id: probeId, version }) {
|
|
58
|
+
log.error(err)
|
|
59
|
+
|
|
60
|
+
onlyUniqueUpdates(STATUSES.ERROR, probeId, version, () => {
|
|
61
|
+
const payload = statusPayload(probeId, version, STATUSES.ERROR)
|
|
62
|
+
|
|
63
|
+
payload.debugger.diagnostics.exception = {
|
|
64
|
+
type: err.code,
|
|
65
|
+
message: err.message,
|
|
66
|
+
stacktrace: err.stack
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
send(payload)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function send (payload) {
|
|
74
|
+
const form = new FormData()
|
|
75
|
+
|
|
76
|
+
form.append(
|
|
77
|
+
'event',
|
|
78
|
+
JSON.stringify(payload),
|
|
79
|
+
{ filename: 'event.json', contentType: 'application/json; charset=utf-8' }
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const options = {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
url: config.url,
|
|
85
|
+
path: '/debugger/v1/diagnostics',
|
|
86
|
+
headers: form.getHeaders()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
request(form, options, (err) => {
|
|
90
|
+
if (err) log.error(err)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function statusPayload (probeId, version, status) {
|
|
95
|
+
return {
|
|
96
|
+
ddsource,
|
|
97
|
+
service,
|
|
98
|
+
debugger: {
|
|
99
|
+
diagnostics: { probeId, runtimeId, version, status }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function onlyUniqueUpdates (type, id, version, fn) {
|
|
105
|
+
const key = `${type}-${id}-${version}`
|
|
106
|
+
if (cache.has(key)) return
|
|
107
|
+
fn()
|
|
108
|
+
cache.set(key)
|
|
109
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { join } = require('path')
|
|
4
|
+
const { Worker, MessageChannel, threadId: parentThreadId } = require('worker_threads')
|
|
5
|
+
const log = require('../log')
|
|
6
|
+
|
|
7
|
+
let worker = null
|
|
8
|
+
let configChannel = null
|
|
9
|
+
|
|
10
|
+
const { NODE_OPTIONS, ...env } = process.env
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
start,
|
|
14
|
+
configure
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function start (config, rc) {
|
|
18
|
+
if (worker !== null) return
|
|
19
|
+
|
|
20
|
+
log.debug('Starting Dynamic Instrumentation client...')
|
|
21
|
+
|
|
22
|
+
const rcAckCallbacks = new Map()
|
|
23
|
+
const rcChannel = new MessageChannel()
|
|
24
|
+
configChannel = new MessageChannel()
|
|
25
|
+
|
|
26
|
+
rc.setProductHandler('LIVE_DEBUGGING', (action, conf, id, ack) => {
|
|
27
|
+
const ackId = `${id}-${conf.version}`
|
|
28
|
+
rcAckCallbacks.set(ackId, ack)
|
|
29
|
+
rcChannel.port2.postMessage({ action, conf, ackId })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
rcChannel.port2.on('message', ({ ackId, error }) => {
|
|
33
|
+
rcAckCallbacks.get(ackId)(error)
|
|
34
|
+
rcAckCallbacks.delete(ackId)
|
|
35
|
+
})
|
|
36
|
+
rcChannel.port2.on('messageerror', (err) => log.error(err))
|
|
37
|
+
|
|
38
|
+
worker = new Worker(
|
|
39
|
+
join(__dirname, 'devtools_client', 'index.js'),
|
|
40
|
+
{
|
|
41
|
+
execArgv: [], // Avoid worker thread inheriting the `-r` command line argument
|
|
42
|
+
env, // Avoid worker thread inheriting the `NODE_OPTIONS` environment variable (in case it contains `-r`)
|
|
43
|
+
workerData: {
|
|
44
|
+
config: serializableConfig(config),
|
|
45
|
+
parentThreadId,
|
|
46
|
+
rcPort: rcChannel.port1,
|
|
47
|
+
configPort: configChannel.port1
|
|
48
|
+
},
|
|
49
|
+
transferList: [rcChannel.port1, configChannel.port1]
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
worker.unref()
|
|
54
|
+
|
|
55
|
+
worker.on('online', () => {
|
|
56
|
+
log.debug(`Dynamic Instrumentation worker thread started successfully (thread id: ${worker.threadId})`)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
worker.on('error', (err) => log.error(err))
|
|
60
|
+
worker.on('messageerror', (err) => log.error(err))
|
|
61
|
+
|
|
62
|
+
worker.on('exit', (code) => {
|
|
63
|
+
const error = new Error(`Dynamic Instrumentation worker thread exited unexpectedly with code ${code}`)
|
|
64
|
+
|
|
65
|
+
log.error(error)
|
|
66
|
+
|
|
67
|
+
// Be nice, clean up now that the worker thread encounted an issue and we can't continue
|
|
68
|
+
rc.removeProductHandler('LIVE_DEBUGGING')
|
|
69
|
+
worker.removeAllListeners()
|
|
70
|
+
configChannel = null
|
|
71
|
+
for (const ackId of rcAckCallbacks.keys()) {
|
|
72
|
+
rcAckCallbacks.get(ackId)(error)
|
|
73
|
+
rcAckCallbacks.delete(ackId)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function configure (config) {
|
|
79
|
+
if (configChannel === null) return
|
|
80
|
+
configChannel.port2.postMessage(serializableConfig(config))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// TODO: Refactor the Config class so it never produces any config objects that are incompatible with MessageChannel
|
|
84
|
+
function serializableConfig (config) {
|
|
85
|
+
// URL objects cannot be serialized over the MessageChannel, so we need to convert them to strings first
|
|
86
|
+
if (config.url instanceof URL) {
|
|
87
|
+
config = { ...config }
|
|
88
|
+
config.url = config.url.toString()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return config
|
|
92
|
+
}
|
|
@@ -43,9 +43,15 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
43
43
|
// length of `payload.events` when calling `makePayload`
|
|
44
44
|
this._eventCount = 0
|
|
45
45
|
|
|
46
|
+
this.metadataTags = {}
|
|
47
|
+
|
|
46
48
|
this.reset()
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
setMetadataTags (tags) {
|
|
52
|
+
this.metadataTags = tags
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
_encodeTestSuite (bytes, content) {
|
|
50
56
|
let keysLength = TEST_SUITE_KEYS_LENGTH
|
|
51
57
|
const itrCorrelationId = content.meta[ITR_CORRELATION_ID]
|
|
@@ -277,6 +283,10 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
_encode (bytes, trace) {
|
|
286
|
+
if (this._isReset) {
|
|
287
|
+
this._encodePayloadStart(bytes)
|
|
288
|
+
this._isReset = false
|
|
289
|
+
}
|
|
280
290
|
const startTime = Date.now()
|
|
281
291
|
|
|
282
292
|
const rawEvents = trace.map(formatSpan)
|
|
@@ -330,7 +340,8 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
330
340
|
'*': {
|
|
331
341
|
language: 'javascript',
|
|
332
342
|
library_version: ddTraceVersion
|
|
333
|
-
}
|
|
343
|
+
},
|
|
344
|
+
...this.metadataTags
|
|
334
345
|
},
|
|
335
346
|
events: []
|
|
336
347
|
}
|
|
@@ -349,6 +360,22 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
349
360
|
this._encodeMapPrefix(bytes, Object.keys(payload.metadata).length)
|
|
350
361
|
this._encodeString(bytes, '*')
|
|
351
362
|
this._encodeMap(bytes, payload.metadata['*'])
|
|
363
|
+
if (payload.metadata.test) {
|
|
364
|
+
this._encodeString(bytes, 'test')
|
|
365
|
+
this._encodeMap(bytes, payload.metadata.test)
|
|
366
|
+
}
|
|
367
|
+
if (payload.metadata.test_suite_end) {
|
|
368
|
+
this._encodeString(bytes, 'test_suite_end')
|
|
369
|
+
this._encodeMap(bytes, payload.metadata.test_suite_end)
|
|
370
|
+
}
|
|
371
|
+
if (payload.metadata.test_module_end) {
|
|
372
|
+
this._encodeString(bytes, 'test_module_end')
|
|
373
|
+
this._encodeMap(bytes, payload.metadata.test_module_end)
|
|
374
|
+
}
|
|
375
|
+
if (payload.metadata.test_session_end) {
|
|
376
|
+
this._encodeString(bytes, 'test_session_end')
|
|
377
|
+
this._encodeMap(bytes, payload.metadata.test_session_end)
|
|
378
|
+
}
|
|
352
379
|
this._encodeString(bytes, 'events')
|
|
353
380
|
// Get offset of the events list to update the length of the array when calling `makePayload`
|
|
354
381
|
this._eventsOffset = bytes.length
|
|
@@ -359,7 +386,7 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
359
386
|
reset () {
|
|
360
387
|
this._reset()
|
|
361
388
|
this._eventCount = 0
|
|
362
|
-
this.
|
|
389
|
+
this._isReset = true
|
|
363
390
|
}
|
|
364
391
|
}
|
|
365
392
|
|
|
@@ -183,7 +183,7 @@ function request (data, options, callback) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
function byteLength (data) {
|
|
186
|
-
return data.length > 0 ? data.reduce((prev, next) => prev + next
|
|
186
|
+
return data.length > 0 ? data.reduce((prev, next) => prev + Buffer.byteLength(next, 'utf8'), 0) : 0
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
Object.defineProperty(request, 'writable', {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sns": {
|
|
3
|
+
"request": [
|
|
4
|
+
"$.Attributes.KmsMasterKeyId",
|
|
5
|
+
"$.Attributes.PlatformCredential",
|
|
6
|
+
"$.Attributes.PlatformPrincipal",
|
|
7
|
+
"$.Attributes.Token",
|
|
8
|
+
"$.AWSAccountId",
|
|
9
|
+
"$.Endpoint",
|
|
10
|
+
"$.OneTimePassword",
|
|
11
|
+
"$.phoneNumber",
|
|
12
|
+
"$.PhoneNumber",
|
|
13
|
+
"$.Token"
|
|
14
|
+
],
|
|
15
|
+
"response": [
|
|
16
|
+
"$.Attributes.KmsMasterKeyId",
|
|
17
|
+
"$.Attributes.Token",
|
|
18
|
+
"$.Endpoints.*.Token",
|
|
19
|
+
"$.PhoneNumber",
|
|
20
|
+
"$.PhoneNumbers",
|
|
21
|
+
"$.phoneNumbers",
|
|
22
|
+
"$.PlatformApplication.*.PlatformCredential",
|
|
23
|
+
"$.PlatformApplication.*.PlatformPrincipal",
|
|
24
|
+
"$.Subscriptions.*.Endpoint"
|
|
25
|
+
],
|
|
26
|
+
"expand": [
|
|
27
|
+
"$.MessageAttributes.*.StringValue"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const aws = require('./aws.json')
|
|
2
|
+
const sdks = { aws }
|
|
3
|
+
|
|
4
|
+
function getSDKRules (sdk, requestInput, responseInput) {
|
|
5
|
+
return Object.fromEntries(
|
|
6
|
+
Object.entries(sdk).map(([service, serviceRules]) => {
|
|
7
|
+
return [
|
|
8
|
+
service,
|
|
9
|
+
{
|
|
10
|
+
request: serviceRules.request.concat(requestInput || []),
|
|
11
|
+
response: serviceRules.response.concat(responseInput || []),
|
|
12
|
+
expand: serviceRules.expand || []
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function appendRules (requestInput, responseInput) {
|
|
20
|
+
return Object.fromEntries(
|
|
21
|
+
Object.entries(sdks).map(([name, sdk]) => {
|
|
22
|
+
return [
|
|
23
|
+
name,
|
|
24
|
+
getSDKRules(sdk, requestInput, responseInput)
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { appendRules }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const rfdc = require('rfdc')({ proto: false, circles: false })
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
PAYLOAD_TAG_REQUEST_PREFIX,
|
|
5
|
+
PAYLOAD_TAG_RESPONSE_PREFIX
|
|
6
|
+
} = require('../constants')
|
|
7
|
+
|
|
8
|
+
const jsonpath = require('jsonpath-plus').JSONPath
|
|
9
|
+
|
|
10
|
+
const { tagsFromObject } = require('./tagging')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Given an identified value, attempt to parse it as JSON if relevant
|
|
14
|
+
*
|
|
15
|
+
* @param {any} value
|
|
16
|
+
* @returns {any} the parsed object if parsing was successful, the input if not
|
|
17
|
+
*/
|
|
18
|
+
function maybeJSONParseValue (value) {
|
|
19
|
+
if (typeof value !== 'string' || value[0] !== '{') {
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(value)
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return value
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply expansion to all expansion JSONPath queries
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} object
|
|
34
|
+
* @param {[String]} expansionRules list of JSONPath queries
|
|
35
|
+
*/
|
|
36
|
+
function expand (object, expansionRules) {
|
|
37
|
+
for (const rule of expansionRules) {
|
|
38
|
+
jsonpath(rule, object, (value, _type, desc) => {
|
|
39
|
+
desc.parent[desc.parentProperty] = maybeJSONParseValue(value)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Apply redaction to all redaction JSONPath queries
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} object
|
|
48
|
+
* @param {[String]} redactionRules
|
|
49
|
+
*/
|
|
50
|
+
function redact (object, redactionRules) {
|
|
51
|
+
for (const rule of redactionRules) {
|
|
52
|
+
jsonpath(rule, object, (_value, _type, desc) => {
|
|
53
|
+
desc.parent[desc.parentProperty] = 'redacted'
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate a map of tag names to tag values by performing:
|
|
60
|
+
* 1. Attempting to parse identified fields as JSON
|
|
61
|
+
* 2. Redacting fields identified by redaction rules
|
|
62
|
+
* 3. Flattening the resulting object, producing as many tag name/tag value pairs
|
|
63
|
+
* as there are leaf values in the object
|
|
64
|
+
* This function performs side-effects on a _copy_ of the input object.
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} config sdk configuration for the service
|
|
67
|
+
* @param {[String]} config.expand expansion rules for the service
|
|
68
|
+
* @param {[String]} config.request redaction rules for the request
|
|
69
|
+
* @param {[String]} config.response redaction rules for the response
|
|
70
|
+
* @param {Object} object the input object to generate tags from
|
|
71
|
+
* @param {Object} opts tag generation options
|
|
72
|
+
* @param {String} opts.prefix prefix for all generated tags
|
|
73
|
+
* @param {number} opts.maxDepth maximum depth to traverse the object
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
function computeTags (config, object, opts) {
|
|
77
|
+
const payload = rfdc(object)
|
|
78
|
+
const redactionRules = opts.prefix === PAYLOAD_TAG_REQUEST_PREFIX ? config.request : config.response
|
|
79
|
+
const expansionRules = config.expand
|
|
80
|
+
expand(payload, expansionRules)
|
|
81
|
+
redact(payload, redactionRules)
|
|
82
|
+
return tagsFromObject(payload, opts)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function tagsFromRequest (config, object, opts) {
|
|
86
|
+
return computeTags(config, object, { ...opts, prefix: PAYLOAD_TAG_REQUEST_PREFIX })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function tagsFromResponse (config, object, opts) {
|
|
90
|
+
return computeTags(config, object, { ...opts, prefix: PAYLOAD_TAG_RESPONSE_PREFIX })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { computeTags, tagsFromRequest, tagsFromResponse }
|