-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathcontextlines.ts
113 lines (99 loc) · 3.1 KB
/
contextlines.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import type { Event, IntegrationFn, StackFrame } from '@sentry/core';
import { LRUMap, addContextToFrame, defineIntegration } from '@sentry/core';
const INTEGRATION_NAME = 'ContextLines';
const FILE_CONTENT_CACHE = new LRUMap<string, string | null>(100);
const DEFAULT_LINES_OF_CONTEXT = 7;
/**
* Resets the file cache. Exists for testing purposes.
* @hidden
*/
export function resetFileContentCache(): void {
FILE_CONTENT_CACHE.clear();
}
/**
* Reads file contents and caches them in a global LRU cache.
*
* @param filename filepath to read content from.
*/
async function readSourceFile(filename: string): Promise<string | null> {
const cachedFile = FILE_CONTENT_CACHE.get(filename);
// We have a cache hit
if (cachedFile !== undefined) {
return cachedFile;
}
let content: string | null = null;
try {
content = await Deno.readTextFile(filename);
} catch (_) {
//
}
FILE_CONTENT_CACHE.set(filename, content);
return content;
}
interface ContextLinesOptions {
/**
* Sets the number of context lines for each frame when loading a file.
* Defaults to 7.
*
* Set to 0 to disable loading and inclusion of source files.
*/
frameContextLines?: number;
}
const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
return {
name: INTEGRATION_NAME,
processEvent(event) {
return addSourceContext(event, contextLines);
},
};
}) satisfies IntegrationFn;
/**
* Adds source context to event stacktraces.
*
* Enabled by default in the Deno SDK.
*
* ```js
* Sentry.init({
* integrations: [
* Sentry.contextLinesIntegration(),
* ],
* })
* ```
*/
export const contextLinesIntegration = defineIntegration(_contextLinesIntegration);
/** Processes an event and adds context lines */
async function addSourceContext(event: Event, contextLines: number): Promise<Event> {
if (contextLines > 0 && event.exception?.values) {
for (const exception of event.exception.values) {
if (exception.stacktrace?.frames) {
await addSourceContextToFrames(exception.stacktrace.frames, contextLines);
}
}
}
return event;
}
/** Adds context lines to frames */
async function addSourceContextToFrames(frames: StackFrame[], contextLines: number): Promise<void> {
for (const frame of frames) {
// Only add context if we have a filename and it hasn't already been added
if (frame.filename && frame.in_app && frame.context_line === undefined) {
const permission = await Deno.permissions.query({
name: 'read',
path: frame.filename,
});
if (permission.state == 'granted') {
const sourceFile = await readSourceFile(frame.filename);
if (sourceFile) {
try {
const lines = sourceFile.split('\n');
addContextToFrame(lines, frame, contextLines);
} catch (_) {
// anomaly, being defensive in case
// unlikely to ever happen in practice but can definitely happen in theory
}
}
}
}
}
}