dd-trace 5.21.0 → 5.23.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 +2 -0
- package/index.d.ts +20 -8
- package/package.json +11 -5
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/body-parser.js +4 -4
- package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
- package/packages/datadog-instrumentations/src/child_process.js +2 -2
- package/packages/datadog-instrumentations/src/connect.js +4 -4
- package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
- package/packages/datadog-instrumentations/src/couchbase.js +12 -12
- package/packages/datadog-instrumentations/src/cucumber.js +294 -56
- package/packages/datadog-instrumentations/src/dns.js +10 -10
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
- package/packages/datadog-instrumentations/src/express.js +4 -4
- package/packages/datadog-instrumentations/src/fastify.js +6 -6
- package/packages/datadog-instrumentations/src/fetch.js +1 -1
- package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
- package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
- package/packages/datadog-instrumentations/src/hapi.js +10 -13
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/http/client.js +3 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/knex.js +2 -2
- package/packages/datadog-instrumentations/src/koa.js +5 -5
- package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
- package/packages/datadog-instrumentations/src/mariadb.js +8 -8
- package/packages/datadog-instrumentations/src/memcached.js +2 -2
- package/packages/datadog-instrumentations/src/microgateway-core.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -53
- package/packages/datadog-instrumentations/src/mocha/utils.js +37 -18
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/mocha.js +4 -0
- package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
- package/packages/datadog-instrumentations/src/mongoose.js +5 -6
- package/packages/datadog-instrumentations/src/mysql.js +3 -3
- package/packages/datadog-instrumentations/src/mysql2.js +6 -6
- package/packages/datadog-instrumentations/src/net.js +2 -2
- package/packages/datadog-instrumentations/src/next.js +5 -5
- package/packages/datadog-instrumentations/src/openai.js +62 -71
- package/packages/datadog-instrumentations/src/oracledb.js +8 -8
- package/packages/datadog-instrumentations/src/passport-http.js +1 -1
- package/packages/datadog-instrumentations/src/passport-local.js +1 -1
- package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +60 -5
- package/packages/datadog-instrumentations/src/pino.js +4 -4
- package/packages/datadog-instrumentations/src/playwright.js +6 -4
- package/packages/datadog-instrumentations/src/redis.js +2 -2
- package/packages/datadog-instrumentations/src/restify.js +4 -4
- package/packages/datadog-instrumentations/src/rhea.js +4 -4
- package/packages/datadog-instrumentations/src/router.js +5 -5
- package/packages/datadog-instrumentations/src/sharedb.js +2 -2
- package/packages/datadog-instrumentations/src/vitest.js +188 -12
- package/packages/datadog-instrumentations/src/winston.js +2 -3
- 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 +39 -10
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-hapi/src/index.js +2 -2
- 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 +18 -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 +85 -65
- 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/datadog-shimmer/src/shimmer.js +144 -10
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/blocking.js +23 -17
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/graphql.js +3 -1
- package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
- 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 +93 -52
- package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
- package/packages/dd-trace/src/appsec/telemetry.js +3 -3
- 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 +15 -1
- package/packages/dd-trace/src/config.js +100 -40
- 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/lambda/handler.js +1 -0
- package/packages/dd-trace/src/lambda/index.js +12 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -6
- 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 +24 -4
- package/packages/dd-trace/src/profiler.js +15 -5
- package/packages/dd-trace/src/profiling/config.js +7 -4
- package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +8 -2
- package/packages/dd-trace/src/profiling/profiler.js +0 -9
- 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 +59 -60
- package/packages/dd-trace/src/proxy.js +31 -24
- package/packages/dd-trace/src/span_stats.js +4 -2
- package/packages/dd-trace/src/telemetry/index.js +23 -6
- package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
- package/packages/dd-trace/src/appsec/rasp.js +0 -176
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('crypto')
|
|
4
|
+
const { breakpoints } = require('./state')
|
|
5
|
+
const session = require('./session')
|
|
6
|
+
const send = require('./send')
|
|
7
|
+
const { ackEmitting } = require('./status')
|
|
8
|
+
const { parentThreadId } = require('./config')
|
|
9
|
+
const log = require('../../log')
|
|
10
|
+
const { version } = require('../../../../../package.json')
|
|
11
|
+
|
|
12
|
+
require('./remote_config')
|
|
13
|
+
|
|
14
|
+
// There doesn't seem to be an official standard for the content of these fields, so we're just populating them with
|
|
15
|
+
// something that should be useful to a Node.js developer.
|
|
16
|
+
const threadId = parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${parentThreadId}`
|
|
17
|
+
const threadName = parentThreadId === 0 ? 'MainThread' : `WorkerThread:${parentThreadId}`
|
|
18
|
+
|
|
19
|
+
session.on('Debugger.paused', async ({ params }) => {
|
|
20
|
+
const start = process.hrtime.bigint()
|
|
21
|
+
const timestamp = Date.now()
|
|
22
|
+
const probes = params.hitBreakpoints.map((id) => breakpoints.get(id))
|
|
23
|
+
await session.post('Debugger.resume')
|
|
24
|
+
const diff = process.hrtime.bigint() - start // TODO: Should this be recored as telemetry?
|
|
25
|
+
|
|
26
|
+
log.debug(`Finished processing breakpoints - main thread paused for: ${Number(diff) / 1000000} ms`)
|
|
27
|
+
|
|
28
|
+
const logger = {
|
|
29
|
+
// We can safely use `location.file` from the first probe in the array, since all probes hit by `hitBreakpoints`
|
|
30
|
+
// must exist in the same file since the debugger can only pause the main thread in one location.
|
|
31
|
+
name: probes[0].location.file, // name of the class/type/file emitting the snapshot
|
|
32
|
+
method: params.callFrames[0].functionName, // name of the method/function emitting the snapshot
|
|
33
|
+
version,
|
|
34
|
+
thread_id: threadId,
|
|
35
|
+
thread_name: threadName
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// TODO: Send multiple probes in one HTTP request as an array
|
|
39
|
+
for (const probe of probes) {
|
|
40
|
+
const snapshot = {
|
|
41
|
+
id: randomUUID(),
|
|
42
|
+
timestamp,
|
|
43
|
+
probe: {
|
|
44
|
+
id: probe.id,
|
|
45
|
+
version: probe.version,
|
|
46
|
+
location: probe.location
|
|
47
|
+
},
|
|
48
|
+
language: 'javascript'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// TODO: Process template
|
|
52
|
+
send(probe.template, logger, snapshot, (err) => {
|
|
53
|
+
if (err) log.error(err)
|
|
54
|
+
else ackEmitting(probe)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { builtinModules } = require('node:module')
|
|
4
|
+
|
|
5
|
+
if (builtinModules.includes('inspector/promises')) {
|
|
6
|
+
module.exports = require('node:inspector/promises')
|
|
7
|
+
} else {
|
|
8
|
+
const inspector = require('node:inspector')
|
|
9
|
+
const { promisify } = require('node:util')
|
|
10
|
+
|
|
11
|
+
// The rest of the code in this file is lifted from:
|
|
12
|
+
// https://github.com/nodejs/node/blob/1d4d76ff3fb08f9a0c55a1d5530b46c4d5d550c7/lib/inspector/promises.js
|
|
13
|
+
class Session extends inspector.Session {
|
|
14
|
+
constructor () { super() } // eslint-disable-line no-useless-constructor
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Session.prototype.post = promisify(inspector.Session.prototype.post)
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
...inspector,
|
|
21
|
+
Session
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -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', {
|
|
@@ -2,4 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const { registerLambdaHook } = require('./runtime/ritm')
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* It is safe to do it this way, since customers will never be expected to disable
|
|
7
|
+
* this specific instrumentation through the init config object.
|
|
8
|
+
*/
|
|
9
|
+
const _DD_TRACE_DISABLED_INSTRUMENTATIONS = process.env.DD_TRACE_DISABLED_INSTRUMENTATIONS || ''
|
|
10
|
+
const _disabledInstrumentations = new Set(
|
|
11
|
+
_DD_TRACE_DISABLED_INSTRUMENTATIONS ? _DD_TRACE_DISABLED_INSTRUMENTATIONS.split(',') : []
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if (!_disabledInstrumentations.has('lambda')) {
|
|
15
|
+
registerLambdaHook()
|
|
16
|
+
}
|
|
@@ -236,8 +236,7 @@ class TextMapPropagator {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
_hasParentIdInTags (spanContext) {
|
|
239
|
-
return tags.DD_PARENT_ID in spanContext._trace.tags
|
|
240
|
-
spanContext._trace.tags[tags.DD_PARENT_ID] !== zeroTraceId
|
|
239
|
+
return tags.DD_PARENT_ID in spanContext._trace.tags
|
|
241
240
|
}
|
|
242
241
|
|
|
243
242
|
_updateParentIdFromDdHeaders (carrier, firstSpanContext) {
|
|
@@ -445,10 +444,6 @@ class TextMapPropagator {
|
|
|
445
444
|
}
|
|
446
445
|
})
|
|
447
446
|
|
|
448
|
-
if (!spanContext._trace.tags[tags.DD_PARENT_ID]) {
|
|
449
|
-
spanContext._trace.tags[tags.DD_PARENT_ID] = zeroTraceId
|
|
450
|
-
}
|
|
451
|
-
|
|
452
447
|
this._extractBaggageItems(carrier, spanContext)
|
|
453
448
|
return spanContext
|
|
454
449
|
}
|
|
@@ -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
|
+
}
|