@@ -62,10 +62,48 @@ export class ContextLines implements Integration {
62
62
63
63
/** Processes an event and adds context lines */
64
64
public async addSourceContext ( event : Event ) : Promise < Event > {
65
+ // keep a lookup map of which files we've already enqueued to read,
66
+ // so we don't enqueue the same file multiple times which would cause multiple i/o reads
67
+ const enqueuedReadSourceFileTasks : Record < string , number > = { } ;
68
+ const readSourceFileTasks : Promise < string | null > [ ] = [ ] ;
69
+
70
+ if ( this . _contextLines > 0 && event . exception ?. values ) {
71
+ for ( const exception of event . exception . values ) {
72
+ if ( ! exception . stacktrace ?. frames ) {
73
+ continue ;
74
+ }
75
+
76
+ // We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache.
77
+ // This ends up prioritizes source context for frames at the top of the stack instead of the bottom.
78
+ for ( let i = exception . stacktrace . frames . length - 1 ; i >= 0 ; i -- ) {
79
+ const frame = exception . stacktrace . frames [ i ] ;
80
+ // Call cache.get to bump the file to the top of the cache and ensure we have not already
81
+ // enqueued a read operation for this filename
82
+ if (
83
+ frame . filename &&
84
+ ! enqueuedReadSourceFileTasks [ frame . filename ] &&
85
+ ! FILE_CONTENT_CACHE . get ( frame . filename )
86
+ ) {
87
+ readSourceFileTasks . push ( _readSourceFile ( frame . filename ) ) ;
88
+ enqueuedReadSourceFileTasks [ frame . filename ] = 1 ;
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ // check if files to read > 0, if so, await all of them to be read before adding source contexts.
95
+ // Normally, Promise.all here could be short circuited if one of the promises rejects, but we
96
+ // are guarding from that by wrapping the i/o read operation in a try/catch.
97
+ if ( readSourceFileTasks . length > 0 ) {
98
+ await Promise . all ( readSourceFileTasks ) ;
99
+ }
100
+
101
+ // Perform the same loop as above, but this time we can assume all files are in the cache
102
+ // and attempt to add source context to frames.
65
103
if ( this . _contextLines > 0 && event . exception ?. values ) {
66
104
for ( const exception of event . exception . values ) {
67
105
if ( exception . stacktrace ?. frames ) {
68
- await this . addSourceContextToFrames ( exception . stacktrace . frames ) ;
106
+ this . addSourceContextToFrames ( exception . stacktrace . frames ) ;
69
107
}
70
108
}
71
109
}
@@ -74,18 +112,16 @@ export class ContextLines implements Integration {
74
112
}
75
113
76
114
/** Adds context lines to frames */
77
- public async addSourceContextToFrames ( frames : StackFrame [ ] ) : Promise < void > {
78
- const contextLines = this . _contextLines ;
79
-
115
+ public addSourceContextToFrames ( frames : StackFrame [ ] ) : void {
80
116
for ( const frame of frames ) {
81
117
// Only add context if we have a filename and it hasn't already been added
82
118
if ( frame . filename && frame . context_line === undefined ) {
83
- const sourceFile = await _readSourceFile ( frame . filename ) ;
119
+ const sourceFile = FILE_CONTENT_CACHE . get ( frame . filename ) ;
84
120
85
121
if ( sourceFile ) {
86
122
try {
87
123
const lines = sourceFile . split ( '\n' ) ;
88
- addContextToFrame ( lines , frame , contextLines ) ;
124
+ addContextToFrame ( lines , frame , this . _contextLines ) ;
89
125
} catch ( e ) {
90
126
// anomaly, being defensive in case
91
127
// unlikely to ever happen in practice but can definitely happen in theory
@@ -109,6 +145,10 @@ async function _readSourceFile(filename: string): Promise<string | null> {
109
145
}
110
146
111
147
let content : string | null = null ;
148
+
149
+ // Guard from throwing if readFile fails, this enables us to use Promise.all and
150
+ // not have it short circuiting if one of the promises rejects + since context lines are added
151
+ // on a best effort basis, we want to throw here anyways.
112
152
try {
113
153
content = await readTextFileAsync ( filename ) ;
114
154
} catch ( _ ) {
0 commit comments