UNPKG

fyipe-staging

Version:

Fyipe is a JS package that tracks error event and send logs from your applications to your fyipe dashboard.

186 lines (180 loc) 7.33 kB
import { readFile } from 'fs'; import * as LRUMap from 'lru_map'; const CONTENT_CACHE = new LRUMap.default.LRUMap(100); class Util { constructor(options) { this.options = options; } getErrorType() { return { INFO: 'info', WARNING: 'warning', ERROR: 'error', }; } async _getErrorStackTrace(errorEvent) { const frames = []; // get error stack trace const stack = errorEvent.stack ? errorEvent.stack : errorEvent.error.stack ? errorEvent.error.stack : errorEvent.error; const stackTrace = stack.split('\n'); // it is all a string so split into array by the enter key // the first item is always the title. const firstStack = stackTrace[0].split(':'); // browser add a : to seperate the title from the description let obj = { type: firstStack[0], message: errorEvent.message ? errorEvent.message : errorEvent.error, stacktrace: null, lineNumber: errorEvent.line || errorEvent.lineno, columnNumber: errorEvent.col, }; // loop through the remaining stack to construct the remaining frame for (let index = 1; index < stackTrace.length; index++) { const currentFrame = stackTrace[index]; // split the string into two const firstHalf = currentFrame.substring( 0, currentFrame.indexOf('(') - 1 ); // first half contains the method const methodName = firstHalf .substring(firstHalf.lastIndexOf(' ')) .replace(/\s+/g, ''); // second half contains file, line number and column number let secondHalf = currentFrame.substring(currentFrame.indexOf('(')); // strip away the () the first and last character secondHalf = secondHalf.substring(1); secondHalf = secondHalf.substring(0, secondHalf.length - 1); // we split the second half by : since the format is filelocation:linenumber:columnnumber secondHalf = secondHalf.split(':'); // we pick the last two as the line number and column number const lineNumber = secondHalf[secondHalf.length - 2]; const columnNumber = secondHalf[secondHalf.length - 1]; // then we merge the rest by the : let fileName = ''; let position = 0; while (position < secondHalf.length - 2) { fileName += `${secondHalf[position]}`; if (position !== secondHalf.length - 3) fileName += ':'; position = position + 1; } // add this onto the frames frames.push({ methodName, lineNumber, columnNumber, fileName, }); } const stacktrace = { frames, }; obj.stacktrace = stacktrace; // check if readFile is supported before attempting to read file, this currently works on only NODE // check if user opted in for getting code snippet before getting it if (readFile && this.options.captureCodeSnippet) { obj = await this._getErrorCodeSnippet(obj); } return obj; } _getUserDeviceDetails() { const deviceDetails = { device: null, browser: null }; if (typeof window !== 'undefined') { const details = window.navigator.appVersion; // get string between first parenthesis const deviceOS = details.substring( details.indexOf('(') + 1, details.indexOf(')') ); const device = deviceOS.split(';'); // get string after last parenthesis const deviceBrowser = details .substring(details.lastIndexOf(')') + 1) .trim() .split(' '); const browser = deviceBrowser[0]; const browserDetails = { name: browser.substring(0, browser.indexOf('/')), version: browser.substring(browser.indexOf('/') + 1), }; deviceDetails.device = device; deviceDetails.browser = browserDetails; } return deviceDetails; } async _getErrorCodeSnippet(errorObj) { const frames = errorObj.stacktrace ? errorObj.stacktrace.frames : []; for (let i = 0; i < frames.length; i++) { let fileName = frames[i].fileName; // check what it starts with fileName = this._formatFileName(fileName); // try to get the file from the cache const cache = CONTENT_CACHE.get(fileName); // if we get a hit for the file if (cache !== undefined) { // and the content is not null if (cache !== null) { frames[i].sourceFile = cache; } } else { // try to read the file content and save to cache const currentContent = await this._readFileFromSource(fileName); if (currentContent !== null) { frames[i].sourceFile = currentContent; } } } frames.map(frame => { const lines = frame.sourceFile ? frame.sourceFile.split('\n') : []; const localFrame = this._addCodeSnippetToFrame(lines, frame); frame = localFrame; return frame; }); errorObj.stacktrace.frames = frames; return errorObj; } _readFileFromSource(fileName) { return new Promise(resolve => { readFile(fileName, (err, data) => { const content = err ? null : data.toString(); CONTENT_CACHE.set(fileName, content); resolve(content); }); }); } _formatFileName(fileName) { const fileIndicator = 'file://'; let localFileName = fileName; if (fileName.indexOf(fileIndicator) > -1) { // check for index of file then trim the file part by skiping it and starting with the leading / localFileName = fileName.substring( fileName.indexOf(fileIndicator) + fileIndicator.length ); } return localFileName; } _addCodeSnippetToFrame(lines, frame, linesOfContext = 5) { if (lines.length < 1) return; const lineNumber = frame.lineNumber || 0; const maxLines = lines.length; const sourceLine = Math.max(Math.min(maxLines, lineNumber - 1), 0); // attach the line before the error frame.linesBeforeError = lines.slice( Math.max(0, sourceLine - linesOfContext), sourceLine ); // attach the line after the error frame.linesAfterError = lines.slice( Math.min(sourceLine + 1, maxLines), sourceLine + 1 + linesOfContext ); // attach the error line frame.errorLine = lines[Math.min(maxLines - 1, sourceLine)]; // remove the source file delete frame.sourceFile; return frame; } } export default Util;