Skip to content

Commit 8271990

Browse files
committed
ref(contextlines): store split file in cache
1 parent 766a00b commit 8271990

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

packages/node/src/integrations/contextlines.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { addContextToFrame } from '@sentry/utils';
33
import { readFile } from 'fs';
44
import { LRUMap } from 'lru_map';
55

6-
const FILE_CONTENT_CACHE = new LRUMap<string, string | null>(100);
6+
const FILE_CONTENT_CACHE = new LRUMap<string, string[] | null>(100);
77
const DEFAULT_LINES_OF_CONTEXT = 7;
88

99
// TODO: Replace with promisify when minimum supported node >= v8
@@ -64,7 +64,7 @@ export class ContextLines implements Integration {
6464
public async addSourceContext(event: Event): Promise<Event> {
6565
if (this._contextLines > 0 && event.exception?.values) {
6666
for (const exception of event.exception.values) {
67-
if (exception.stacktrace?.frames) {
67+
if (exception.stacktrace && exception.stacktrace.frames) {
6868
await this.addSourceContextToFrames(exception.stacktrace.frames);
6969
}
7070
}
@@ -80,12 +80,11 @@ export class ContextLines implements Integration {
8080
for (const frame of frames) {
8181
// Only add context if we have a filename and it hasn't already been added
8282
if (frame.filename && frame.context_line === undefined) {
83-
const sourceFile = await _readSourceFile(frame.filename);
83+
const sourceFileLines = await _readSourceFile(frame.filename);
8484

85-
if (sourceFile) {
85+
if (sourceFileLines) {
8686
try {
87-
const lines = sourceFile.split('\n');
88-
addContextToFrame(lines, frame, contextLines);
87+
addContextToFrame(sourceFileLines, frame, contextLines);
8988
} catch (e) {
9089
// anomaly, being defensive in case
9190
// unlikely to ever happen in practice but can definitely happen in theory
@@ -98,21 +97,30 @@ export class ContextLines implements Integration {
9897

9998
/**
10099
* Reads file contents and caches them in a global LRU cache.
100+
* If reading fails, mark the file as null in the cache so we don't try again.
101101
*
102102
* @param filename filepath to read content from.
103103
*/
104-
async function _readSourceFile(filename: string): Promise<string | null> {
104+
async function _readSourceFile(filename: string): Promise<string[] | null> {
105105
const cachedFile = FILE_CONTENT_CACHE.get(filename);
106-
// We have a cache hit
106+
107+
// We have already attempted to read this file and failed, do not try again
108+
if (cachedFile === null) {
109+
return null;
110+
}
111+
112+
// We have a cache hit, return it
107113
if (cachedFile !== undefined) {
108114
return cachedFile;
109115
}
110116

111-
let content: string | null = null;
117+
// File is neither in cache nor marked as failed, attempt to read it
118+
let content: string[] | null = null;
112119
try {
113-
content = await readTextFileAsync(filename);
120+
const rawFileContents = await readTextFileAsync(filename);
121+
content = rawFileContents.split('\n');
114122
} catch (_) {
115-
//
123+
// if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
116124
}
117125

118126
FILE_CONTENT_CACHE.set(filename, content);

packages/node/test/context-lines.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,31 @@ describe('ContextLines', () => {
107107
expect(readFileSpy).toHaveBeenCalledTimes(0);
108108
});
109109
});
110+
test.only('does not attempt to readfile multiple times if it fails', async () => {
111+
expect.assertions(1);
112+
contextLines = new ContextLines({});
113+
114+
readFileSpy.mockImplementation(() => {
115+
throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'");
116+
});
117+
118+
await addContext([
119+
{
120+
colno: 1,
121+
filename: '/does/not/exist.js',
122+
lineno: 1,
123+
function: 'fxn1',
124+
},
125+
]);
126+
await addContext([
127+
{
128+
colno: 1,
129+
filename: '/does/not/exist.js',
130+
lineno: 1,
131+
function: 'fxn1',
132+
},
133+
]);
134+
135+
expect(readFileSpy).toHaveBeenCalledTimes(1);
136+
});
110137
});

0 commit comments

Comments
 (0)