@@ -62,10 +62,48 @@ export class ContextLines implements Integration {
6262
6363 /** Processes an event and adds context lines */
6464 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.
65103 if ( this . _contextLines > 0 && event . exception ?. values ) {
66104 for ( const exception of event . exception . values ) {
67105 if ( exception . stacktrace ?. frames ) {
68- await this . addSourceContextToFrames ( exception . stacktrace . frames ) ;
106+ this . addSourceContextToFrames ( exception . stacktrace . frames ) ;
69107 }
70108 }
71109 }
@@ -74,18 +112,16 @@ export class ContextLines implements Integration {
74112 }
75113
76114 /** 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 {
80116 for ( const frame of frames ) {
81117 // Only add context if we have a filename and it hasn't already been added
82118 if ( frame . filename && frame . context_line === undefined ) {
83- const sourceFile = await _readSourceFile ( frame . filename ) ;
119+ const sourceFile = FILE_CONTENT_CACHE . get ( frame . filename ) ;
84120
85121 if ( sourceFile ) {
86122 try {
87123 const lines = sourceFile . split ( '\n' ) ;
88- addContextToFrame ( lines , frame , contextLines ) ;
124+ addContextToFrame ( lines , frame , this . _contextLines ) ;
89125 } catch ( e ) {
90126 // anomaly, being defensive in case
91127 // unlikely to ever happen in practice but can definitely happen in theory
@@ -109,6 +145,10 @@ async function _readSourceFile(filename: string): Promise<string | null> {
109145 }
110146
111147 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.
112152 try {
113153 content = await readTextFileAsync ( filename ) ;
114154 } catch ( _ ) {
0 commit comments