From 781e7298298e0dd54df22b0dfbbfdc458304fc0a Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 9 Jul 2024 14:52:21 +0200 Subject: [PATCH] Add support for Dynamic Instrumentation --- packages/dd-trace/src/config.js | 6 +- .../src/debugger/devtools_client/config.js | 26 ++++ .../src/debugger/devtools_client/index.js | 42 +++++++ .../inspector_promises_polyfill.js | 23 ++++ .../debugger/devtools_client/remote_config.js | 119 ++++++++++++++++++ .../src/debugger/devtools_client/send.js | 60 +++++++++ .../src/debugger/devtools_client/session.js | 7 ++ .../src/debugger/devtools_client/state.js | 24 ++++ .../src/debugger/devtools_client/status.js | 89 +++++++++++++ packages/dd-trace/src/debugger/index.js | 62 +++++++++ packages/dd-trace/src/proxy.js | 6 + packages/dd-trace/test/config.spec.js | 11 ++ 12 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 packages/dd-trace/src/debugger/devtools_client/config.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/index.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/remote_config.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/send.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/session.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/state.js create mode 100644 packages/dd-trace/src/debugger/devtools_client/status.js create mode 100644 packages/dd-trace/src/debugger/index.js diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index d8df053e163..9ad701e9faf 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -3,7 +3,7 @@ const fs = require('fs') const os = require('os') const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress -const URL = require('url').URL +const { URL } = require('url') const log = require('./log') const pkg = require('./pkg') const coalesce = require('koalas') @@ -498,6 +498,7 @@ class Config { this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1') this._setValue(defaults, 'dogstatsd.port', '8125') this._setValue(defaults, 'dsmEnabled', false) + this._setValue(defaults, 'dynamicInstrumentationEnabled', false) this._setValue(defaults, 'env', undefined) this._setValue(defaults, 'experimental.enableGetRumData', false) this._setValue(defaults, 'experimental.exporter', undefined) @@ -589,6 +590,7 @@ class Config { DD_DBM_PROPAGATION_MODE, DD_DOGSTATSD_HOSTNAME, DD_DOGSTATSD_PORT, + DD_DYNAMIC_INSTRUMENTATION_ENABLED, DD_ENV, DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED, DD_EXPERIMENTAL_PROFILING_ENABLED, @@ -696,6 +698,7 @@ class Config { this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOSTNAME) this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT) this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) + this._setBoolean(env, 'dynamicInstrumentationEnabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) this._setString(env, 'env', DD_ENV || tags.env) this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED) this._setString(env, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER) @@ -837,6 +840,7 @@ class Config { this._setString(opts, 'dogstatsd.port', options.dogstatsd.port) } this._setBoolean(opts, 'dsmEnabled', options.dsmEnabled) + this._setBoolean(opts, 'dynamicInstrumentationEnabled', options.dynamicInstrumentationEnabled) this._setString(opts, 'env', options.env || tags.env) this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental && options.experimental.enableGetRumData) diff --git a/packages/dd-trace/src/debugger/devtools_client/config.js b/packages/dd-trace/src/debugger/devtools_client/config.js new file mode 100644 index 00000000000..16cde3fe1b2 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/config.js @@ -0,0 +1,26 @@ +'use strict' + +const { workerData: { config: parentConfig, configPort } } = require('node:worker_threads') +const { URL, format } = require('node:url') + +const config = module.exports = { + runtimeId: parentConfig.tags['runtime-id'], + service: parentConfig.service +} + +updateUrl(parentConfig) + +configPort.on('message', updateUrl) + +function updateUrl (updates) { + // if (!updates.url && !updates.hostname && !updates.port) return + + const url = updates.url || new URL(format({ + // TODO: Can this ever be anything other than `http:`, and if so, how do we get the configured value? + protocol: config.url?.protocol || 'http:', + hostname: updates.hostname || config.url?.hostname || 'localhost', + port: updates.port || config.url?.port + })) + + config.url = url.toString() +} diff --git a/packages/dd-trace/src/debugger/devtools_client/index.js b/packages/dd-trace/src/debugger/devtools_client/index.js new file mode 100644 index 00000000000..37c5858c440 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/index.js @@ -0,0 +1,42 @@ +'use strict' + +const uuid = require('crypto-randomuuid') +const { breakpoints } = require('./state') +const session = require('./session') +const send = require('./send') +const { ackEmitting } = require('./status') +require('./remote_config') +const log = require('../../log') + +// The `session.connectToMainThread()` method called above doesn't "register" any active handles, so the worker thread +// will exit with code 0 once when reaches the end of the file unless we do something to keep it alive: +setInterval(() => {}, 1000 * 60) + +session.on('Debugger.paused', async ({ params }) => { + const start = process.hrtime.bigint() + const timestamp = Date.now() + const probes = params.hitBreakpoints.map((id) => breakpoints.get(id)) + await session.post('Debugger.resume') + const diff = process.hrtime.bigint() - start // TODO: Should we log this using some sort of telemetry? + + log.debug(`Finished processing breakpoints - thread paused for: ${Number(diff) / 1000000} ms`) + + // TODO: Is this the correct way of handling multiple breakpoints hit at the same time? + for (const probe of probes) { + await send({ + message: probe.template, // TODO: Process template + snapshot: { + id: uuid(), + timestamp, + probe: { + id: probe.id, + version: probe.version, // TODO: Should this always be 2??? + location: probe.location + }, + language: 'javascript' + } + }) + + ackEmitting(probe) + } +}) diff --git a/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js b/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js new file mode 100644 index 00000000000..bb4b0340be6 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js @@ -0,0 +1,23 @@ +'use strict' + +const { builtinModules } = require('node:module') + +if (builtinModules.includes('inspector/promises')) { + module.exports = require('node:inspector/promises') +} else { + const inspector = require('node:inspector') + const { promisify } = require('node:util') + + // The rest of the code in this file is lifted from: + // https://github.com/nodejs/node/blob/1d4d76ff3fb08f9a0c55a1d5530b46c4d5d550c7/lib/inspector/promises.js + class Session extends inspector.Session { + constructor () { super() } // eslint-disable-line no-useless-constructor + } + + Session.prototype.post = promisify(inspector.Session.prototype.post) + + module.exports = { + ...inspector, + Session + } +} diff --git a/packages/dd-trace/src/debugger/devtools_client/remote_config.js b/packages/dd-trace/src/debugger/devtools_client/remote_config.js new file mode 100644 index 00000000000..47c82f09725 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/remote_config.js @@ -0,0 +1,119 @@ +'use strict' + +const { workerData: { rcPort } } = require('node:worker_threads') +const { scripts, probes, breakpoints } = require('./state') +const session = require('./session') +const { ackReceived, ackInstalled, ackError } = require('./status') +const log = require('../../log') + +let sessionStarted = false + +// Example `probe`: +// { +// id: '100c9a5c-45ad-49dc-818b-c570d31e11d1', +// version: 0, +// type: 'LOG_PROBE', +// language: 'javascript', // ignore +// where: { +// sourceFile: 'index.js', +// lines: ['25'] // only use first array element +// }, +// tags: [], // not used +// template: 'Hello World 2', +// segments: [{ str: 'Hello World 2' }], +// captureSnapshot: true, +// capture: { maxReferenceDepth: 1 }, +// sampling: { snapshotsPerSecond: 1 }, +// evaluateAt: 'EXIT' // only used for method probes +// } +rcPort.on('message', async ({ action, conf: probe }) => { + try { + await processMsg(action, probe) + } catch (err) { + ackError(err, probe) + } +}) + +async function start () { + sessionStarted = true + await session.post('Debugger.enable') +} + +async function stop () { + sessionStarted = false + await session.post('Debugger.disable') +} + +async function processMsg (action, probe) { + log.debug(`Received request to ${action} ${probe.type} probe (id: ${probe.id}, version: ${probe.version})`) + + ackReceived(probe) + + if (probe.type !== 'LOG_PROBE') { + throw new Error(`Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`) + // TODO: Throw also if method log probe + } + + switch (action) { + case 'unapply': + await removeBreakpoint(probe) + break + case 'apply': + await addBreakpoint(probe) + break + case 'modify': + // TODO: Can we modify in place? + await removeBreakpoint(probe) + await addBreakpoint(probe) + break + default: + throw new Error( + `Cannot process probe ${probe.id} (version: ${probe.version}) - unknown remote configuration action: ${action}` + ) + } +} + +async function addBreakpoint (probe) { + if (!sessionStarted) await start() + + // Optimize for sending data to /debugger/v1/input endpoint + probe.location = { + file: probe.where.sourceFile, + lines: [Number(probe.where.lines[0])] // Tracer doesn't support multiple-line breakpoints + } + delete probe.where + + // TODO: Figure out what to do about the full path + const path = `file:///Users/thomas.watson/go/src/github.com/DataDog/debugger-demos/nodejs/${probe.location.file}` + + const { breakpointId } = await session.post('Debugger.setBreakpoint', { + location: { + scriptId: scripts.get(path), + lineNumber: probe.location.lines[0] - 1 // Beware! lineNumber is zero-indexed + } + // TODO: Support conditions + // condition: "request.params.name === 'break'" + }) + + probes.set(probe.id, breakpointId) + breakpoints.set(breakpointId, probe) + + ackInstalled(probe) +} + +async function removeBreakpoint ({ id }) { + if (!sessionStarted) { + // We should not get in this state, but abort if we do so the code doesn't throw + throw Error(`Cannot remove probe ${id}: Debugger not started`) + } + if (!probes.has(id)) { + throw Error(`Unknown probe id: ${id}`) + } + + const breakpointId = probes.get(id) + await session.post('Debugger.removeBreakpoint', { breakpointId }) + probes.delete(id) + breakpoints.delete(breakpointId) + + if (breakpoints.size === 0) await stop() +} diff --git a/packages/dd-trace/src/debugger/devtools_client/send.js b/packages/dd-trace/src/debugger/devtools_client/send.js new file mode 100644 index 00000000000..d6346136b74 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/send.js @@ -0,0 +1,60 @@ +'use strict' + +const { hostname } = require('node:os') +const { inspect } = require('node:util') +const config = require('./config') +const request = require('../../exporters/common/request') + +const host = hostname() +const service = config.service +const ddsource = 'dd_debugger' + +// TODO: Figure out correct logger values +const logger = { + name: __filename, + method: '', + thread_name: `${process.argv0};pid:${process.pid}`, + thread_id: 42, + version: 2 +} + +module.exports = async function send ({ message, snapshot: { id, timestamp, captures, probe, language } }) { + const opts = { + method: 'POST', + url: config.url, + path: '/debugger/v1/input', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + // Accept: 'text/plain' // TODO: This seems wrong (from Python tracer) + } + } + + const payload = { + service, + ddsource, + message, + logger, + // 'dd.trace_id': null, + // 'dd.span_id': null, + // method, + 'debugger.snapshot': { + id, + timestamp, + captures, + // evaluationErrors: [{ expr: 'foo == 42', message: 'foo' }], // TODO: This doesn't seem to be documented? + probe, + language + }, + host, // TODO: This doesn't seem to be documented? + timestamp: Date.now() // TODO: This doesn't seem to be documented? + // ddtags: {} // TODO: This doesn't seem to be documented? + } + + process._rawDebug('Payload:', inspect(payload, { depth: null, colors: true })) // TODO: Remove + + request(JSON.stringify(payload), opts, (err, data, statusCode) => { + if (err) throw err // TODO: Handle error + process._rawDebug('Response:', { statusCode }) // TODO: Remove + process._rawDebug('Response body:', JSON.parse(data)) // TODO: Remove + }) +} diff --git a/packages/dd-trace/src/debugger/devtools_client/session.js b/packages/dd-trace/src/debugger/devtools_client/session.js new file mode 100644 index 00000000000..3cda2322b36 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/session.js @@ -0,0 +1,7 @@ +'use strict' + +const inspector = require('./inspector_promises_polyfill') + +const session = module.exports = new inspector.Session() + +session.connectToMainThread() diff --git a/packages/dd-trace/src/debugger/devtools_client/state.js b/packages/dd-trace/src/debugger/devtools_client/state.js new file mode 100644 index 00000000000..0caf9aa8ab6 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/state.js @@ -0,0 +1,24 @@ +'use strict' + +const session = require('./session') + +const scripts = new Map() + +module.exports = { + scripts, + probes: new Map(), + breakpoints: new Map() +} + +// Known params.url protocols: +// - `node:` - Ignored, as we don't want to instrument Node.js internals +// - `wasm:` - Ignored, as we don't support instrumenting WebAssembly +// - `file:` - Regular on disk file +// Unknown params.url values: +// - `structured-stack` - Not sure what this is, but should just be ignored +// - `` - Not sure what this is, but should just be ignored +session.on('Debugger.scriptParsed', ({ params }) => { + if (params.url.startsWith('file:')) { + scripts.set(params.url, params.scriptId) + } +}) diff --git a/packages/dd-trace/src/debugger/devtools_client/status.js b/packages/dd-trace/src/debugger/devtools_client/status.js new file mode 100644 index 00000000000..ed41cddfa97 --- /dev/null +++ b/packages/dd-trace/src/debugger/devtools_client/status.js @@ -0,0 +1,89 @@ +'use strict' + +const { inspect } = require('node:util') +const config = require('./config') +const request = require('../../exporters/common/request') +const FormData = require('../../exporters/common/form-data') +const log = require('../../log') + +module.exports = { + ackReceived, + ackInstalled, + ackEmitting, + ackError +} + +const service = config.service +const runtimeId = config.runtimeId + +const STATUSES = { + RECEIVED: 'RECEIVED', + INSTALLED: 'INSTALLED', + EMITTING: 'EMITTING', + WARNING: 'WARNING', + ERROR: 'ERROR', + BLOCKED: 'BLOCKED' +} + +function ackReceived ({ id: probeId, version }) { + send(statusPayload(probeId, version, STATUSES.RECEIVED)) +} + +function ackInstalled ({ id: probeId, version }) { + send(statusPayload(probeId, version, STATUSES.INSTALLED)) +} + +function ackEmitting ({ id: probeId, version }) { + send(statusPayload(probeId, version, STATUSES.EMITTING)) +} + +function ackError (err, { id: probeId, version }) { + log.error(err) // TODO: Is there a standard format og loggering errors from the tracer? + + const payload = statusPayload(probeId, version, STATUSES.ERROR) + + payload.debugger.diagnostics.exception = { + type: err.code, + message: err.message, + stacktrace: err.stack + } + + send(payload) +} + +function send (payload) { + process._rawDebug('Diagnostics request event.json:', inspect(payload, { depth: null, colors: true })) // TODO: Remove + + const form = new FormData() + + form.append( + 'event', + JSON.stringify(payload), + { filename: 'event.json', contentType: 'application/json; charset=utf-8' } + ) + + const options = { + method: 'POST', + url: config.url, + path: '/debugger/v1/diagnostics', + headers: form.getHeaders() + } + + process._rawDebug('Diagnostics request options:', options) // TODO: Remove + + request(form, options, (err, data, statusCode) => { + if (err) throw err // TODO: Handle error + process._rawDebug('Response:', { statusCode }) // TODO: Remove + process._rawDebug('Response body:', JSON.parse(data)) // TODO: Remove + }) +} + +function statusPayload (probeId, version, status) { + return { + ddsource: 'dd_debugger', + service, + debugger: { + diagnostics: { probeId, runtimeId, version, status } + } + } +} diff --git a/packages/dd-trace/src/debugger/index.js b/packages/dd-trace/src/debugger/index.js new file mode 100644 index 00000000000..be06ffde070 --- /dev/null +++ b/packages/dd-trace/src/debugger/index.js @@ -0,0 +1,62 @@ +'use strict' + +const { join } = require('node:path') +const { Worker, MessageChannel } = require('node:worker_threads') +const log = require('../log') + +let worker = null +let configChannel = null + +module.exports = { + start, + configure +} + +function start (config, rc) { + if (worker !== null) return + + log.debug('Starting Dynamic Instrumentation client...') + + rc.on('LIVE_DEBUGGING', (action, conf) => { + rcChannel.port2.postMessage({ action, conf }) + }) + + rc.on('LIVE_DEBUGGING_SYMBOL_DB', (action, conf) => { + // TODO: Implement + process._rawDebug('-- RC: LIVE_DEBUGGING_SYMBOL_DB', action, conf) + }) + + const rcChannel = new MessageChannel() + configChannel = new MessageChannel() + + worker = new Worker( + join(__dirname, 'devtools_client', 'index.js'), + { + execArgv: [], // Avoid worker thread inheriting the `-r` command line argument + workerData: { config, rcPort: rcChannel.port1, configPort: configChannel.port1 }, + transferList: [rcChannel.port1, configChannel.port1] + } + ) + + worker.unref() + + worker.on('online', () => { + log.debug(`Dynamic Instrumentation worker thread started successfully (thread id: ${worker.threadId})`) + }) + + // TODO: How should we handle errors? + worker.on('error', (err) => process._rawDebug('DevTools client error:', err)) + + // TODO: How should we handle exits? + worker.on('exit', (code) => { + log.debug(`Dynamic Instrumentation worker thread exited with code ${code}`) + if (code !== 0) { + throw new Error(`DevTools client stopped with unexpected exit code: ${code}`) + } + }) +} + +function configure (config) { + if (configChannel === null) return + configChannel.port2.postMessage(config) +} diff --git a/packages/dd-trace/src/proxy.js b/packages/dd-trace/src/proxy.js index 7f3a0e81780..5335f91555b 100644 --- a/packages/dd-trace/src/proxy.js +++ b/packages/dd-trace/src/proxy.js @@ -5,6 +5,7 @@ const Config = require('./config') const runtimeMetrics = require('./runtime_metrics') const log = require('./log') const { setStartupLogPluginManager } = require('./startup-log') +const DynamicInstrumentation = require('./debugger') const telemetry = require('./telemetry') const nomenclature = require('./service-naming') const PluginManager = require('./plugin_manager') @@ -111,6 +112,10 @@ class Tracer extends NoopProxy { this._flare.enable(config) this._flare.module.send(conf.args) }) + + if (config.dynamicInstrumentationEnabled) { + DynamicInstrumentation.start(config, rc) + } } if (config.isGCPFunction || config.isAzureFunction) { @@ -195,6 +200,7 @@ class Tracer extends NoopProxy { if (this._tracingInitialized) { this._tracer.configure(config) this._pluginManager.configure(config) + DynamicInstrumentation.configure(config) setStartupLogPluginManager(this._pluginManager) } } diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index bf639804f23..8ee68eaf827 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -219,6 +219,7 @@ describe('Config', () => { expect(config).to.have.property('reportHostname', false) expect(config).to.have.property('scope', undefined) expect(config).to.have.property('logLevel', 'debug') + expect(config).to.have.property('dynamicInstrumentationEnabled', false) expect(config).to.have.property('traceId128BitGenerationEnabled', true) expect(config).to.have.property('traceId128BitLoggingEnabled', false) expect(config).to.have.property('spanAttributeSchema', 'v0') @@ -294,6 +295,7 @@ describe('Config', () => { { name: 'dogstatsd.hostname', value: '127.0.0.1', origin: 'calculated' }, { name: 'dogstatsd.port', value: '8125', origin: 'default' }, { name: 'dsmEnabled', value: false, origin: 'default' }, + { name: 'dynamicInstrumentationEnabled', value: false, origin: 'default' }, { name: 'env', value: undefined, origin: 'default' }, { name: 'experimental.enableGetRumData', value: false, origin: 'default' }, { name: 'experimental.exporter', value: undefined, origin: 'default' }, @@ -422,6 +424,7 @@ describe('Config', () => { process.env.DD_TRACE_CLIENT_IP_HEADER = 'x-true-client-ip' process.env.DD_RUNTIME_METRICS_ENABLED = 'true' process.env.DD_TRACE_REPORT_HOSTNAME = 'true' + process.env.DD_DYNAMIC_INSTRUMENTATION_ENABLED = 'true' process.env.DD_ENV = 'test' process.env.DD_TRACE_GLOBAL_TAGS = 'foo:bar,baz:qux' process.env.DD_TRACE_SAMPLE_RATE = '0.5' @@ -504,6 +507,7 @@ describe('Config', () => { expect(config).to.have.property('clientIpHeader', 'x-true-client-ip') expect(config).to.have.property('runtimeMetrics', true) expect(config).to.have.property('reportHostname', true) + expect(config).to.have.property('dynamicInstrumentationEnabled', true) expect(config).to.have.property('env', 'test') expect(config).to.have.property('sampleRate', 0.5) expect(config).to.have.property('traceId128BitGenerationEnabled', true) @@ -600,6 +604,7 @@ describe('Config', () => { { name: 'clientIpHeader', value: 'x-true-client-ip', origin: 'env_var' }, { name: 'dogstatsd.hostname', value: 'dsd-agent', origin: 'env_var' }, { name: 'dogstatsd.port', value: '5218', origin: 'env_var' }, + { name: 'dynamicInstrumentationEnabled', value: true, origin: 'env_var' }, { name: 'env', value: 'test', origin: 'env_var' }, { name: 'experimental.enableGetRumData', value: true, origin: 'env_var' }, { name: 'experimental.exporter', value: 'log', origin: 'env_var' }, @@ -725,6 +730,7 @@ describe('Config', () => { }, service: 'service', version: '0.1.0', + dynamicInstrumentationEnabled: true, env: 'test', clientIpEnabled: true, clientIpHeader: 'x-true-client-ip', @@ -798,6 +804,7 @@ describe('Config', () => { expect(config).to.have.nested.property('dogstatsd.port', '5218') expect(config).to.have.property('service', 'service') expect(config).to.have.property('version', '0.1.0') + expect(config).to.have.property('dynamicInstrumentationEnabled', true) expect(config).to.have.property('env', 'test') expect(config).to.have.property('sampleRate', 0.5) expect(config).to.have.property('logger', logger) @@ -869,6 +876,7 @@ describe('Config', () => { { name: 'clientIpHeader', value: 'x-true-client-ip', origin: 'code' }, { name: 'dogstatsd.hostname', value: 'agent-dsd', origin: 'code' }, { name: 'dogstatsd.port', value: '5218', origin: 'code' }, + { name: 'dynamicInstrumentationEnabled', value: true, origin: 'code' }, { name: 'env', value: 'test', origin: 'code' }, { name: 'experimental.enableGetRumData', value: true, origin: 'code' }, { name: 'experimental.exporter', value: 'log', origin: 'code' }, @@ -1039,6 +1047,7 @@ describe('Config', () => { process.env.DD_VERSION = '0.0.0' process.env.DD_RUNTIME_METRICS_ENABLED = 'true' process.env.DD_TRACE_REPORT_HOSTNAME = 'true' + process.env.DD_DYNAMIC_INSTRUMENTATION_ENABLED = 'true' process.env.DD_ENV = 'test' process.env.DD_API_KEY = '123' process.env.DD_TRACE_SPAN_ATTRIBUTE_SCHEMA = 'v0' @@ -1092,6 +1101,7 @@ describe('Config', () => { flushMinSpans: 500, service: 'test', version: '1.0.0', + dynamicInstrumentationEnabled: false, env: 'development', clientIpEnabled: true, clientIpHeader: 'x-true-client-ip', @@ -1168,6 +1178,7 @@ describe('Config', () => { expect(config).to.have.property('flushMinSpans', 500) expect(config).to.have.property('service', 'test') expect(config).to.have.property('version', '1.0.0') + expect(config).to.have.property('dynamicInstrumentationEnabled', false) expect(config).to.have.property('env', 'development') expect(config).to.have.property('clientIpEnabled', true) expect(config).to.have.property('clientIpHeader', 'x-true-client-ip')