dd-trace
Version:
Datadog APM tracing client for JavaScript
157 lines (130 loc) • 4.62 kB
JavaScript
const {
workerData: {
config,
breakpointSetChannel,
breakpointHitChannel,
breakpointRemoveChannel,
},
} = require('worker_threads')
const { randomUUID } = require('crypto')
// TODO: move debugger/devtools_client/session to common place
const session = require('../../../debugger/devtools_client/session')
// TODO: move debugger/devtools_client/source-maps to common place
const { getGeneratedPosition } = require('../../../debugger/devtools_client/source-maps')
// TODO: move debugger/devtools_client/snapshot to common place
const { getLocalStateForCallFrame } = require('../../../debugger/devtools_client/snapshot')
const {
DEFAULT_MAX_REFERENCE_DEPTH,
DEFAULT_MAX_COLLECTION_SIZE,
DEFAULT_MAX_FIELD_COUNT,
DEFAULT_MAX_LENGTH,
} = require('../../../debugger/devtools_client/snapshot/constants')
// TODO: move debugger/devtools_client/state to common place
const {
findScriptFromPartialPath,
getStackFromCallFrames,
} = require('../../../debugger/devtools_client/state')
const log = require('../../../log')
const processTags = require('../../../process-tags')
let sessionStarted = false
const breakpointIdToProbe = new Map()
const probeIdToBreakpointId = new Map()
const limits = {
maxReferenceDepth: DEFAULT_MAX_REFERENCE_DEPTH,
maxCollectionSize: DEFAULT_MAX_COLLECTION_SIZE,
maxFieldCount: DEFAULT_MAX_FIELD_COUNT,
maxLength: DEFAULT_MAX_LENGTH,
}
session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint], callFrames } }) => {
const probe = breakpointIdToProbe.get(hitBreakpoint)
if (!probe) {
log.warn('No probe found for breakpoint', hitBreakpoint)
return session.post('Debugger.resume')
}
const stack = await getStackFromCallFrames(callFrames)
const { processLocalState } = await getLocalStateForCallFrame(callFrames[0], limits)
await session.post('Debugger.resume')
const snapshot = {
id: randomUUID(),
timestamp: Date.now(),
probe: {
id: probe.id,
version: '0',
location: probe.location,
},
captures: {
lines: { [probe.location.lines[0]]: { locals: processLocalState() } },
},
stack,
language: 'javascript',
}
if (config.propagateProcessTags?.enabled) {
snapshot[processTags.DYNAMIC_INSTRUMENTATION_FIELD_NAME] = processTags.tagsObject
}
breakpointHitChannel.postMessage({ snapshot })
})
breakpointRemoveChannel.on('message', async (probeId) => {
await removeBreakpoint(probeId)
breakpointRemoveChannel.postMessage(probeId)
})
breakpointSetChannel.on('message', async (probe) => {
await addBreakpoint(probe)
breakpointSetChannel.postMessage(probe.id)
})
async function removeBreakpoint (probeId) {
if (!sessionStarted) {
// We should not get in this state, but abort if we do, so the code doesn't fail unexpected
throw new Error(`Cannot remove probe ${probeId}: Debugger not started`)
}
const breakpointId = probeIdToBreakpointId.get(probeId)
if (!breakpointId) {
throw new Error(`Unknown probe id: ${probeId}`)
}
await session.post('Debugger.removeBreakpoint', { breakpointId })
probeIdToBreakpointId.delete(probeId)
breakpointIdToProbe.delete(breakpointId)
}
async function addBreakpoint (probe) {
if (!sessionStarted) await start()
const { file, line } = probe
probe.location = { file, lines: [String(line)] }
const script = findScriptFromPartialPath(file)
if (!script) {
log.error('No loaded script found for', file)
throw new Error(`No loaded script found for ${file}`)
}
const { url, scriptId, sourceMapURL, source } = script
log.warn('Adding breakpoint at %s:%s', url, line)
let lineNumber = line
let columnNumber = 0
if (sourceMapURL) {
try {
({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, line, sourceMapURL))
} catch (err) {
log.error('Error processing script with source map', err)
}
if (lineNumber === null) {
log.error('Could not find generated position for %s:%s', url, line)
lineNumber = line
columnNumber = 0
}
}
try {
const { breakpointId } = await session.post('Debugger.setBreakpoint', {
location: {
scriptId,
lineNumber: lineNumber - 1,
columnNumber,
},
})
breakpointIdToProbe.set(breakpointId, probe)
probeIdToBreakpointId.set(probe.id, breakpointId)
} catch (e) {
log.error('Error setting breakpoint at %s:%s', url, line, e)
}
}
function start () {
sessionStarted = true
return session.post('Debugger.enable') // return instead of await to reduce number of promises created
}