155 lines
5.5 KiB
JavaScript
155 lines
5.5 KiB
JavaScript
var {
|
|
_optionalChain
|
|
} = require('@sentry/utils');
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
const fs = require('fs');
|
|
const core = require('@sentry/core');
|
|
const utils = require('@sentry/utils');
|
|
|
|
const FILE_CONTENT_CACHE = new utils.LRUMap(100);
|
|
const DEFAULT_LINES_OF_CONTEXT = 7;
|
|
const INTEGRATION_NAME = 'ContextLines';
|
|
|
|
// TODO: Replace with promisify when minimum supported node >= v8
|
|
function readTextFileAsync(path) {
|
|
return new Promise((resolve, reject) => {
|
|
fs.readFile(path, 'utf8', (err, data) => {
|
|
if (err) reject(err);
|
|
else resolve(data);
|
|
});
|
|
});
|
|
}
|
|
|
|
const _contextLinesIntegration = ((options = {}) => {
|
|
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
|
|
|
|
return {
|
|
name: INTEGRATION_NAME,
|
|
// TODO v8: Remove this
|
|
setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
processEvent(event) {
|
|
return addSourceContext(event, contextLines);
|
|
},
|
|
};
|
|
}) ;
|
|
|
|
const contextLinesIntegration = core.defineIntegration(_contextLinesIntegration);
|
|
|
|
/**
|
|
* Add node modules / packages to the event.
|
|
* @deprecated Use `contextLinesIntegration()` instead.
|
|
*/
|
|
// eslint-disable-next-line deprecation/deprecation
|
|
const ContextLines = core.convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration)
|
|
|
|
;
|
|
|
|
async function addSourceContext(event, contextLines) {
|
|
// keep a lookup map of which files we've already enqueued to read,
|
|
// so we don't enqueue the same file multiple times which would cause multiple i/o reads
|
|
const enqueuedReadSourceFileTasks = {};
|
|
const readSourceFileTasks = [];
|
|
|
|
if (contextLines > 0 && _optionalChain([event, 'access', _2 => _2.exception, 'optionalAccess', _3 => _3.values])) {
|
|
for (const exception of event.exception.values) {
|
|
if (!_optionalChain([exception, 'access', _4 => _4.stacktrace, 'optionalAccess', _5 => _5.frames])) {
|
|
continue;
|
|
}
|
|
|
|
// We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache.
|
|
// This ends up prioritizes source context for frames at the top of the stack instead of the bottom.
|
|
for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) {
|
|
const frame = exception.stacktrace.frames[i];
|
|
// Call cache.get to bump the file to the top of the cache and ensure we have not already
|
|
// enqueued a read operation for this filename
|
|
if (frame.filename && !enqueuedReadSourceFileTasks[frame.filename] && !FILE_CONTENT_CACHE.get(frame.filename)) {
|
|
readSourceFileTasks.push(_readSourceFile(frame.filename));
|
|
enqueuedReadSourceFileTasks[frame.filename] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if files to read > 0, if so, await all of them to be read before adding source contexts.
|
|
// Normally, Promise.all here could be short circuited if one of the promises rejects, but we
|
|
// are guarding from that by wrapping the i/o read operation in a try/catch.
|
|
if (readSourceFileTasks.length > 0) {
|
|
await Promise.all(readSourceFileTasks);
|
|
}
|
|
|
|
// Perform the same loop as above, but this time we can assume all files are in the cache
|
|
// and attempt to add source context to frames.
|
|
if (contextLines > 0 && _optionalChain([event, 'access', _6 => _6.exception, 'optionalAccess', _7 => _7.values])) {
|
|
for (const exception of event.exception.values) {
|
|
if (exception.stacktrace && exception.stacktrace.frames) {
|
|
await addSourceContextToFrames(exception.stacktrace.frames, contextLines);
|
|
}
|
|
}
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
/** Adds context lines to frames */
|
|
function addSourceContextToFrames(frames, contextLines) {
|
|
for (const frame of frames) {
|
|
// Only add context if we have a filename and it hasn't already been added
|
|
if (frame.filename && frame.context_line === undefined) {
|
|
const sourceFileLines = FILE_CONTENT_CACHE.get(frame.filename);
|
|
|
|
if (sourceFileLines) {
|
|
try {
|
|
utils.addContextToFrame(sourceFileLines, frame, contextLines);
|
|
} catch (e) {
|
|
// anomaly, being defensive in case
|
|
// unlikely to ever happen in practice but can definitely happen in theory
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line deprecation/deprecation
|
|
|
|
/**
|
|
* Reads file contents and caches them in a global LRU cache.
|
|
* If reading fails, mark the file as null in the cache so we don't try again.
|
|
*
|
|
* @param filename filepath to read content from.
|
|
*/
|
|
async function _readSourceFile(filename) {
|
|
const cachedFile = FILE_CONTENT_CACHE.get(filename);
|
|
|
|
// We have already attempted to read this file and failed, do not try again
|
|
if (cachedFile === null) {
|
|
return null;
|
|
}
|
|
|
|
// We have a cache hit, return it
|
|
if (cachedFile !== undefined) {
|
|
return cachedFile;
|
|
}
|
|
|
|
// Guard from throwing if readFile fails, this enables us to use Promise.all and
|
|
// not have it short circuiting if one of the promises rejects + since context lines are added
|
|
// on a best effort basis, we want to throw here anyways.
|
|
|
|
// If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it
|
|
let content = null;
|
|
try {
|
|
const rawFileContents = await readTextFileAsync(filename);
|
|
content = rawFileContents.split('\n');
|
|
} catch (_) {
|
|
// if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
|
|
}
|
|
|
|
FILE_CONTENT_CACHE.set(filename, content);
|
|
return content;
|
|
}
|
|
|
|
exports.ContextLines = ContextLines;
|
|
exports.contextLinesIntegration = contextLinesIntegration;
|
|
//# sourceMappingURL=contextlines.js.map
|