UNPKG

@sentry/node

Version:
165 lines (140 loc) 5.78 kB
var { _optionalChain } = require('@sentry/utils/cjs/buildPolyfills'); Object.defineProperty(exports, '__esModule', { value: true }); const utils = require('@sentry/utils'); const fs = require('fs'); const lru_map = require('lru_map'); const FILE_CONTENT_CACHE = new lru_map.LRUMap(100); const DEFAULT_LINES_OF_CONTEXT = 7; // 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); }); }); } /** Add node modules / packages to the event */ class ContextLines { /** * @inheritDoc */ static __initStatic() {this.id = 'ContextLines';} /** * @inheritDoc */ __init() {this.name = ContextLines.id;} constructor( _options = {}) {this._options = _options;ContextLines.prototype.__init.call(this);} /** Get's the number of context lines to add */ get _contextLines() { return this._options.frameContextLines !== undefined ? this._options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; } /** * @inheritDoc */ setupOnce(addGlobalEventProcessor, getCurrentHub) { addGlobalEventProcessor(event => { const self = getCurrentHub().getIntegration(ContextLines); if (!self) { return event; } return this.addSourceContext(event); }); } /** Processes an event and adds context lines */ async addSourceContext(event) { // 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 (this._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 (this._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 this.addSourceContextToFrames(exception.stacktrace.frames); } } } return event; } /** Adds context lines to frames */ addSourceContextToFrames(frames) { 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, this._contextLines); } catch (e) { // anomaly, being defensive in case // unlikely to ever happen in practice but can definitely happen in theory } } } } } }ContextLines.__initStatic(); /** * 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; //# sourceMappingURL=contextlines.js.map