1
- import { Event , EventProcessor , Exception , Hub , Integration , StackFrame , StackParser } from '@sentry/types' ;
1
+ import {
2
+ ClientOptions ,
3
+ Event ,
4
+ EventProcessor ,
5
+ Exception ,
6
+ Hub ,
7
+ Integration ,
8
+ StackFrame ,
9
+ StackParser ,
10
+ } from '@sentry/types' ;
2
11
import { Debugger , InspectorNotification , Runtime , Session } from 'inspector' ;
3
12
import { LRUMap } from 'lru_map' ;
4
13
14
+ export interface DebugSession {
15
+ /** Configures and connects to the debug session */
16
+ configureAndConnect ( onPause : ( message : InspectorNotification < Debugger . PausedEventDataType > ) => void ) : void ;
17
+ /** Gets local variables for an objectId */
18
+ getLocalVariables ( objectId : string ) : Promise < Record < string , unknown > > ;
19
+ }
20
+
5
21
/**
6
22
* Promise API is available as `Experimental` and in Node 19 only.
7
23
*
@@ -11,8 +27,38 @@ import { LRUMap } from 'lru_map';
11
27
* https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api
12
28
* https://nodejs.org/docs/latest-v14.x/api/inspector.html
13
29
*/
14
- class AsyncSession extends Session {
15
- public getProperties ( objectId : string ) : Promise < Runtime . PropertyDescriptor [ ] > {
30
+ class AsyncSession extends Session implements DebugSession {
31
+ /** @inheritdoc */
32
+ public configureAndConnect ( onPause : ( message : InspectorNotification < Debugger . PausedEventDataType > ) => void ) : void {
33
+ this . connect ( ) ;
34
+ this . on ( 'Debugger.paused' , onPause ) ;
35
+ this . post ( 'Debugger.enable' ) ;
36
+ // We only want to pause on uncaught exceptions
37
+ this . post ( 'Debugger.setPauseOnExceptions' , { state : 'uncaught' } ) ;
38
+ }
39
+
40
+ /** @inheritdoc */
41
+ public async getLocalVariables ( objectId : string ) : Promise < Record < string , unknown > > {
42
+ const props = await this . _getProperties ( objectId ) ;
43
+ const unrolled : Record < string , unknown > = { } ;
44
+
45
+ for ( const prop of props ) {
46
+ if ( prop ?. value ?. objectId && prop ?. value . className === 'Array' ) {
47
+ unrolled [ prop . name ] = await this . _unrollArray ( prop . value . objectId ) ;
48
+ } else if ( prop ?. value ?. objectId && prop ?. value ?. className === 'Object' ) {
49
+ unrolled [ prop . name ] = await this . _unrollObject ( prop . value . objectId ) ;
50
+ } else if ( prop ?. value ?. value || prop ?. value ?. description ) {
51
+ unrolled [ prop . name ] = prop . value . value || `<${ prop . value . description } >` ;
52
+ }
53
+ }
54
+
55
+ return unrolled ;
56
+ }
57
+
58
+ /**
59
+ * Gets all the PropertyDescriptors of an object
60
+ */
61
+ private _getProperties ( objectId : string ) : Promise < Runtime . PropertyDescriptor [ ] > {
16
62
return new Promise ( ( resolve , reject ) => {
17
63
this . post (
18
64
'Runtime.getProperties' ,
@@ -30,6 +76,30 @@ class AsyncSession extends Session {
30
76
) ;
31
77
} ) ;
32
78
}
79
+
80
+ /**
81
+ * Unrolls an array property
82
+ */
83
+ private async _unrollArray ( objectId : string ) : Promise < unknown > {
84
+ const props = await this . _getProperties ( objectId ) ;
85
+ return props
86
+ . filter ( v => v . name !== 'length' && ! isNaN ( parseInt ( v . name , 10 ) ) )
87
+ . sort ( ( a , b ) => parseInt ( a . name , 10 ) - parseInt ( b . name , 10 ) )
88
+ . map ( v => v ?. value ?. value ) ;
89
+ }
90
+
91
+ /**
92
+ * Unrolls an object property
93
+ */
94
+ private async _unrollObject ( objectId : string ) : Promise < Record < string , unknown > > {
95
+ const props = await this . _getProperties ( objectId ) ;
96
+ return props
97
+ . map < [ string , unknown ] > ( v => [ v . name , v ?. value ?. value ] )
98
+ . reduce ( ( obj , [ key , val ] ) => {
99
+ obj [ key ] = val ;
100
+ return obj ;
101
+ } , { } as Record < string , unknown > ) ;
102
+ }
33
103
}
34
104
35
105
// Add types for the exception event data
@@ -60,7 +130,7 @@ function hashFrames(frames: StackFrame[] | undefined): string | undefined {
60
130
return frames . slice ( - 10 ) . reduce ( ( acc , frame ) => `${ acc } ,${ frame . function } ,${ frame . lineno } ,${ frame . colno } ` , '' ) ;
61
131
}
62
132
63
- interface FrameVariables {
133
+ export interface FrameVariables {
64
134
function : string ;
65
135
vars ?: Record < string , unknown > ;
66
136
}
@@ -73,24 +143,27 @@ export class LocalVariables implements Integration {
73
143
74
144
public readonly name : string = LocalVariables . id ;
75
145
76
- private readonly _session : AsyncSession = new AsyncSession ( ) ;
77
146
private readonly _cachedFrames : LRUMap < string , Promise < FrameVariables [ ] > > = new LRUMap ( 20 ) ;
78
147
private _stackParser : StackParser | undefined ;
79
148
149
+ public constructor ( private readonly _session : DebugSession = new AsyncSession ( ) ) { }
150
+
80
151
/**
81
152
* @inheritDoc
82
153
*/
83
154
public setupOnce ( addGlobalEventProcessor : ( callback : EventProcessor ) => void , getCurrentHub : ( ) => Hub ) : void {
84
- const options = getCurrentHub ( ) . getClient ( ) ?. getOptions ( ) ;
155
+ this . _setup ( addGlobalEventProcessor , getCurrentHub ( ) . getClient ( ) ?. getOptions ( ) ) ;
156
+ }
85
157
86
- if ( options ?. _experiments ?. includeStackLocals ) {
87
- this . _stackParser = options . stackParser ;
158
+ /** Setup in a way that's easier to call from tests */
159
+ private _setup (
160
+ addGlobalEventProcessor : ( callback : EventProcessor ) => void ,
161
+ clientOptions : ClientOptions | undefined ,
162
+ ) : void {
163
+ if ( clientOptions ?. _experiments ?. includeStackLocals ) {
164
+ this . _stackParser = clientOptions . stackParser ;
88
165
89
- this . _session . connect ( ) ;
90
- this . _session . on ( 'Debugger.paused' , this . _handlePaused . bind ( this ) ) ;
91
- this . _session . post ( 'Debugger.enable' ) ;
92
- // We only want to pause on uncaught exceptions
93
- this . _session . post ( 'Debugger.setPauseOnExceptions' , { state : 'uncaught' } ) ;
166
+ this . _session . configureAndConnect ( this . _handlePaused . bind ( this ) ) ;
94
167
95
168
addGlobalEventProcessor ( async event => this . _addLocalVariables ( event ) ) ;
96
169
}
@@ -134,7 +207,7 @@ export class LocalVariables implements Integration {
134
207
return { function : fn } ;
135
208
}
136
209
137
- const vars = await this . _unrollProps ( await this . _session . getProperties ( localScope . object . objectId ) ) ;
210
+ const vars = await this . _session . getLocalVariables ( localScope . object . objectId ) ;
138
211
139
212
return { function : fn , vars } ;
140
213
} ) ;
@@ -144,49 +217,6 @@ export class LocalVariables implements Integration {
144
217
this . _cachedFrames . set ( exceptionHash , Promise . all ( framePromises ) ) ;
145
218
}
146
219
147
- /**
148
- * Unrolls all the properties
149
- */
150
- private async _unrollProps ( props : Runtime . PropertyDescriptor [ ] ) : Promise < Record < string , unknown > > {
151
- const unrolled : Record < string , unknown > = { } ;
152
-
153
- for ( const prop of props ) {
154
- if ( prop ?. value ?. objectId && prop ?. value . className === 'Array' ) {
155
- unrolled [ prop . name ] = await this . _unrollArray ( prop . value . objectId ) ;
156
- } else if ( prop ?. value ?. objectId && prop ?. value ?. className === 'Object' ) {
157
- unrolled [ prop . name ] = await this . _unrollObject ( prop . value . objectId ) ;
158
- } else if ( prop ?. value ?. value || prop ?. value ?. description ) {
159
- unrolled [ prop . name ] = prop . value . value || `<${ prop . value . description } >` ;
160
- }
161
- }
162
-
163
- return unrolled ;
164
- }
165
-
166
- /**
167
- * Unrolls an array property
168
- */
169
- private async _unrollArray ( objectId : string ) : Promise < unknown > {
170
- const props = await this . _session . getProperties ( objectId ) ;
171
- return props
172
- . filter ( v => v . name !== 'length' && ! isNaN ( parseInt ( v . name , 10 ) ) )
173
- . sort ( ( a , b ) => parseInt ( a . name , 10 ) - parseInt ( b . name , 10 ) )
174
- . map ( v => v ?. value ?. value ) ;
175
- }
176
-
177
- /**
178
- * Unrolls an object property
179
- */
180
- private async _unrollObject ( objectId : string ) : Promise < Record < string , unknown > > {
181
- const props = await this . _session . getProperties ( objectId ) ;
182
- return props
183
- . map < [ string , unknown ] > ( v => [ v . name , v ?. value ?. value ] )
184
- . reduce ( ( obj , [ key , val ] ) => {
185
- obj [ key ] = val ;
186
- return obj ;
187
- } , { } as Record < string , unknown > ) ;
188
- }
189
-
190
220
/**
191
221
* Adds local variables event stack frames.
192
222
*/
@@ -209,14 +239,13 @@ export class LocalVariables implements Integration {
209
239
}
210
240
211
241
// Check if we have local variables for an exception that matches the hash
212
- const cachedFrames = await this . _cachedFrames . get ( hash ) ;
242
+ // delete is identical to get but also removes the entry from the cache
243
+ const cachedFrames = await this . _cachedFrames . delete ( hash ) ;
213
244
214
245
if ( cachedFrames === undefined ) {
215
246
return ;
216
247
}
217
248
218
- await this . _cachedFrames . delete ( hash ) ;
219
-
220
249
const frameCount = exception . stacktrace ?. frames ?. length || 0 ;
221
250
222
251
for ( let i = 0 ; i < frameCount ; i ++ ) {
0 commit comments