UNPKG

8.49 kBJavaScriptView Raw
1import { addContextToFrame, basename, dirname, SyncPromise } from '@sentry/utils';
2import { readFile } from 'fs';
3import { LRUMap } from 'lru_map';
4import * as stacktrace from './stacktrace';
5// tslint:disable-next-line:no-unsafe-any
6var DEFAULT_LINES_OF_CONTEXT = 7;
7var FILE_CONTENT_CACHE = new LRUMap(100);
8/**
9 * Resets the file cache. Exists for testing purposes.
10 * @hidden
11 */
12export function resetFileContentCache() {
13 FILE_CONTENT_CACHE.clear();
14}
15/** JSDoc */
16function getFunction(frame) {
17 try {
18 return frame.functionName || frame.typeName + "." + (frame.methodName || '<anonymous>');
19 }
20 catch (e) {
21 // This seems to happen sometimes when using 'use strict',
22 // stemming from `getTypeName`.
23 // [TypeError: Cannot read property 'constructor' of undefined]
24 return '<anonymous>';
25 }
26}
27var mainModule = ((require.main && require.main.filename && dirname(require.main.filename)) ||
28 global.process.cwd()) + "/";
29/** JSDoc */
30function getModule(filename, base) {
31 if (!base) {
32 base = mainModule; // tslint:disable-line:no-parameter-reassignment
33 }
34 // It's specifically a module
35 var file = basename(filename, '.js');
36 filename = dirname(filename); // tslint:disable-line:no-parameter-reassignment
37 var n = filename.lastIndexOf('/node_modules/');
38 if (n > -1) {
39 // /node_modules/ is 14 chars
40 return filename.substr(n + 14).replace(/\//g, '.') + ":" + file;
41 }
42 // Let's see if it's a part of the main module
43 // To be a part of main module, it has to share the same base
44 n = (filename + "/").lastIndexOf(base, 0);
45 if (n === 0) {
46 var moduleName = filename.substr(base.length).replace(/\//g, '.');
47 if (moduleName) {
48 moduleName += ':';
49 }
50 moduleName += file;
51 return moduleName;
52 }
53 return file;
54}
55/**
56 * This function reads file contents and caches them in a global LRU cache.
57 * Returns a Promise filepath => content array for all files that we were able to read.
58 *
59 * @param filenames Array of filepaths to read content from.
60 */
61function readSourceFiles(filenames) {
62 // we're relying on filenames being de-duped already
63 if (filenames.length === 0) {
64 return SyncPromise.resolve({});
65 }
66 return new SyncPromise(function (resolve) {
67 var sourceFiles = {};
68 var count = 0;
69 var _loop_1 = function (i) {
70 var filename = filenames[i];
71 var cache = FILE_CONTENT_CACHE.get(filename);
72 // We have a cache hit
73 if (cache !== undefined) {
74 // If it's not null (which means we found a file and have a content)
75 // we set the content and return it later.
76 if (cache !== null) {
77 sourceFiles[filename] = cache;
78 }
79 count++;
80 // In any case we want to skip here then since we have a content already or we couldn't
81 // read the file and don't want to try again.
82 if (count === filenames.length) {
83 resolve(sourceFiles);
84 }
85 return "continue";
86 }
87 readFile(filename, function (err, data) {
88 var content = err ? null : data.toString();
89 sourceFiles[filename] = content;
90 // We always want to set the cache, even to null which means there was an error reading the file.
91 // We do not want to try to read the file again.
92 FILE_CONTENT_CACHE.set(filename, content);
93 count++;
94 if (count === filenames.length) {
95 resolve(sourceFiles);
96 }
97 });
98 };
99 // tslint:disable-next-line:prefer-for-of
100 for (var i = 0; i < filenames.length; i++) {
101 _loop_1(i);
102 }
103 });
104}
105/**
106 * @hidden
107 */
108export function extractStackFromError(error) {
109 var stack = stacktrace.parse(error);
110 if (!stack) {
111 return [];
112 }
113 return stack;
114}
115/**
116 * @hidden
117 */
118export function parseStack(stack, options) {
119 var filesToRead = [];
120 var linesOfContext = options && options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
121 var frames = stack.map(function (frame) {
122 var parsedFrame = {
123 colno: frame.columnNumber,
124 filename: frame.fileName || '',
125 function: getFunction(frame),
126 lineno: frame.lineNumber,
127 };
128 var isInternal = frame.native ||
129 (parsedFrame.filename &&
130 !parsedFrame.filename.startsWith('/') &&
131 !parsedFrame.filename.startsWith('.') &&
132 parsedFrame.filename.indexOf(':\\') !== 1);
133 // in_app is all that's not an internal Node function or a module within node_modules
134 // note that isNative appears to return true even for node core libraries
135 // see https://github.com/getsentry/raven-node/issues/176
136 parsedFrame.in_app =
137 !isInternal && parsedFrame.filename !== undefined && parsedFrame.filename.indexOf('node_modules/') === -1;
138 // Extract a module name based on the filename
139 if (parsedFrame.filename) {
140 parsedFrame.module = getModule(parsedFrame.filename);
141 if (!isInternal && linesOfContext > 0) {
142 filesToRead.push(parsedFrame.filename);
143 }
144 }
145 return parsedFrame;
146 });
147 // We do an early return if we do not want to fetch context liens
148 if (linesOfContext <= 0) {
149 return SyncPromise.resolve(frames);
150 }
151 try {
152 return addPrePostContext(filesToRead, frames, linesOfContext);
153 }
154 catch (_) {
155 // This happens in electron for example where we are not able to read files from asar.
156 // So it's fine, we recover be just returning all frames without pre/post context.
157 return SyncPromise.resolve(frames);
158 }
159}
160/**
161 * This function tries to read the source files + adding pre and post context (source code)
162 * to a frame.
163 * @param filesToRead string[] of filepaths
164 * @param frames StackFrame[] containg all frames
165 */
166function addPrePostContext(filesToRead, frames, linesOfContext) {
167 return new SyncPromise(function (resolve) {
168 return readSourceFiles(filesToRead).then(function (sourceFiles) {
169 var result = frames.map(function (frame) {
170 if (frame.filename && sourceFiles[frame.filename]) {
171 try {
172 var lines = sourceFiles[frame.filename].split('\n');
173 addContextToFrame(lines, frame, linesOfContext);
174 }
175 catch (e) {
176 // anomaly, being defensive in case
177 // unlikely to ever happen in practice but can definitely happen in theory
178 }
179 }
180 return frame;
181 });
182 resolve(result);
183 });
184 });
185}
186/**
187 * @hidden
188 */
189export function getExceptionFromError(error, options) {
190 var name = error.name || error.constructor.name;
191 var stack = extractStackFromError(error);
192 return new SyncPromise(function (resolve) {
193 return parseStack(stack, options).then(function (frames) {
194 var result = {
195 stacktrace: {
196 frames: prepareFramesForEvent(frames),
197 },
198 type: name,
199 value: error.message,
200 };
201 resolve(result);
202 });
203 });
204}
205/**
206 * @hidden
207 */
208export function parseError(error, options) {
209 return new SyncPromise(function (resolve) {
210 return getExceptionFromError(error, options).then(function (exception) {
211 resolve({
212 exception: {
213 values: [exception],
214 },
215 });
216 });
217 });
218}
219/**
220 * @hidden
221 */
222export function prepareFramesForEvent(stack) {
223 if (!stack || !stack.length) {
224 return [];
225 }
226 var localStack = stack;
227 var firstFrameFunction = localStack[0].function || '';
228 if (firstFrameFunction.indexOf('captureMessage') !== -1 || firstFrameFunction.indexOf('captureException') !== -1) {
229 localStack = localStack.slice(1);
230 }
231 // The frame where the crash happened, should be the last entry in the array
232 return localStack.reverse();
233}
234//# sourceMappingURL=parsers.js.map
\No newline at end of file