1
1
import type { Debugger , InspectorNotification , Runtime , Session } from 'node:inspector' ;
2
2
import { defineIntegration , getClient } from '@sentry/core' ;
3
- import type { Event , Exception , IntegrationFn , StackParser } from '@sentry/types' ;
4
- import { LRUMap , logger } from '@sentry/utils' ;
3
+ import type { Event , EventHint , Exception , IntegrationFn } from '@sentry/types' ;
4
+ import { logger } from '@sentry/utils' ;
5
5
6
6
import { NODE_MAJOR } from '../../nodeVersion' ;
7
7
import type { NodeClient } from '../../sdk/client' ;
@@ -12,7 +12,8 @@ import type {
12
12
RateLimitIncrement ,
13
13
Variables ,
14
14
} from './common' ;
15
- import { createRateLimiter , functionNamesMatch , hashFrames , hashFromStack } from './common' ;
15
+ import { LOCAL_VARIABLES_KEY } from './common' ;
16
+ import { createRateLimiter , functionNamesMatch } from './common' ;
16
17
17
18
type OnPauseEvent = InspectorNotification < Debugger . PausedEventDataType > ;
18
19
export interface DebugSession {
@@ -22,6 +23,8 @@ export interface DebugSession {
22
23
setPauseOnExceptions ( captureAll : boolean ) : void ;
23
24
/** Gets local variables for an objectId */
24
25
getLocalVariables ( objectId : string , callback : ( vars : Variables ) => void ) : void ;
26
+ /** Sets the local variables on the error object for later retrieval */
27
+ setLocalVarsOnError ( objectId : string , localVariables : FrameVariables [ ] ) : void ;
25
28
}
26
29
27
30
type Next < T > = ( result : T ) => void ;
@@ -128,6 +131,13 @@ class AsyncSession implements DebugSession {
128
131
} ) ;
129
132
}
130
133
134
+ public setLocalVarsOnError ( objectId : string , localVariables : FrameVariables [ ] ) : void {
135
+ this . _session . post ( 'Runtime.callFunctionOn' , {
136
+ functionDeclaration : `function() { this.${ LOCAL_VARIABLES_KEY } = ${ JSON . stringify ( localVariables ) } ; }` ,
137
+ objectId,
138
+ } ) ;
139
+ }
140
+
131
141
/**
132
142
* Gets all the PropertyDescriptors of an object
133
143
*/
@@ -209,25 +219,10 @@ const _localVariablesSyncIntegration = ((
209
219
options : LocalVariablesIntegrationOptions = { } ,
210
220
sessionOverride ?: DebugSession ,
211
221
) => {
212
- const cachedFrames : LRUMap < string , FrameVariables [ ] > = new LRUMap ( 20 ) ;
213
222
let rateLimiter : RateLimitIncrement | undefined ;
214
223
let shouldProcessEvent = false ;
215
224
216
- function addLocalVariablesToException ( exception : Exception ) : void {
217
- const hash = hashFrames ( exception ?. stacktrace ?. frames ) ;
218
-
219
- if ( hash === undefined ) {
220
- return ;
221
- }
222
-
223
- // Check if we have local variables for an exception that matches the hash
224
- // remove is identical to get but also removes the entry from the cache
225
- const cachedFrame = cachedFrames . remove ( hash ) ;
226
-
227
- if ( cachedFrame === undefined ) {
228
- return ;
229
- }
230
-
225
+ function addLocalVariablesToException ( exception : Exception , localVariables : FrameVariables [ ] ) : void {
231
226
// Filter out frames where the function name is `new Promise` since these are in the error.stack frames
232
227
// but do not appear in the debugger call frames
233
228
const frames = ( exception . stacktrace ?. frames || [ ] ) . filter ( frame => frame . function !== 'new Promise' ) ;
@@ -236,32 +231,39 @@ const _localVariablesSyncIntegration = ((
236
231
// Sentry frames are in reverse order
237
232
const frameIndex = frames . length - i - 1 ;
238
233
239
- const cachedFrameVariable = cachedFrame [ i ] ;
240
- const frameVariable = frames [ frameIndex ] ;
234
+ const frameLocalVariables = localVariables [ i ] ;
235
+ const frame = frames [ frameIndex ] ;
241
236
242
237
// Drop out if we run out of frames to match up
243
- if ( ! frameVariable || ! cachedFrameVariable ) {
238
+ if ( ! frame || ! frameLocalVariables ) {
244
239
break ;
245
240
}
246
241
247
242
if (
248
243
// We need to have vars to add
249
- cachedFrameVariable . vars === undefined ||
244
+ frameLocalVariables . vars === undefined ||
250
245
// We're not interested in frames that are not in_app because the vars are not relevant
251
- frameVariable . in_app === false ||
246
+ frame . in_app === false ||
252
247
// The function names need to match
253
- ! functionNamesMatch ( frameVariable . function , cachedFrameVariable . function )
248
+ ! functionNamesMatch ( frame . function , frameLocalVariables . function )
254
249
) {
255
250
continue ;
256
251
}
257
252
258
- frameVariable . vars = cachedFrameVariable . vars ;
253
+ frame . vars = frameLocalVariables . vars ;
259
254
}
260
255
}
261
256
262
- function addLocalVariablesToEvent ( event : Event ) : Event {
263
- for ( const exception of event ?. exception ?. values || [ ] ) {
264
- addLocalVariablesToException ( exception ) ;
257
+ function addLocalVariablesToEvent ( event : Event , hint : EventHint ) : Event {
258
+ if (
259
+ hint . originalException &&
260
+ typeof hint . originalException === 'object' &&
261
+ LOCAL_VARIABLES_KEY in hint . originalException &&
262
+ Array . isArray ( hint . originalException [ LOCAL_VARIABLES_KEY ] )
263
+ ) {
264
+ for ( const exception of event . exception ?. values || [ ] ) {
265
+ addLocalVariablesToException ( exception , hint . originalException [ LOCAL_VARIABLES_KEY ] ) ;
266
+ }
265
267
}
266
268
267
269
return event ;
@@ -289,7 +291,6 @@ const _localVariablesSyncIntegration = ((
289
291
AsyncSession . create ( sessionOverride ) . then (
290
292
session => {
291
293
function handlePaused (
292
- stackParser : StackParser ,
293
294
{ params : { reason, data, callFrames } } : InspectorNotification < PausedExceptionEvent > ,
294
295
complete : ( ) => void ,
295
296
) : void {
@@ -300,16 +301,15 @@ const _localVariablesSyncIntegration = ((
300
301
301
302
rateLimiter ?.( ) ;
302
303
303
- // data.description contains the original error.stack
304
- const exceptionHash = hashFromStack ( stackParser , data ?. description ) ;
304
+ const objectId = data ?. objectId ;
305
305
306
- if ( exceptionHash == undefined ) {
306
+ if ( objectId == undefined ) {
307
307
complete ( ) ;
308
308
return ;
309
309
}
310
310
311
311
const { add, next } = createCallbackList < FrameVariables [ ] > ( frames => {
312
- cachedFrames . set ( exceptionHash , frames ) ;
312
+ session . setLocalVarsOnError ( objectId , frames ) ;
313
313
complete ( ) ;
314
314
} ) ;
315
315
@@ -347,8 +347,7 @@ const _localVariablesSyncIntegration = ((
347
347
const captureAll = options . captureAllExceptions !== false ;
348
348
349
349
session . configureAndConnect (
350
- ( ev , complete ) =>
351
- handlePaused ( clientOptions . stackParser , ev as InspectorNotification < PausedExceptionEvent > , complete ) ,
350
+ ( ev , complete ) => handlePaused ( ev as InspectorNotification < PausedExceptionEvent > , complete ) ,
352
351
captureAll ,
353
352
) ;
354
353
@@ -377,20 +376,13 @@ const _localVariablesSyncIntegration = ((
377
376
} ,
378
377
) ;
379
378
} ,
380
- processEvent ( event : Event ) : Event {
379
+ processEvent ( event : Event , hint : EventHint ) : Event {
381
380
if ( shouldProcessEvent ) {
382
- return addLocalVariablesToEvent ( event ) ;
381
+ return addLocalVariablesToEvent ( event , hint ) ;
383
382
}
384
383
385
384
return event ;
386
385
} ,
387
- // These are entirely for testing
388
- _getCachedFramesCount ( ) : number {
389
- return cachedFrames . size ;
390
- } ,
391
- _getFirstCachedFrame ( ) : FrameVariables [ ] | undefined {
392
- return cachedFrames . values ( ) [ 0 ] ;
393
- } ,
394
386
} ;
395
387
} ) satisfies IntegrationFn ;
396
388
0 commit comments