Skip to content

Commit 6bca76e

Browse files
timfishAbhiPrasad
andauthored
feat(node): Add option to capture local variables for caught exceptions via LocalVariables integration (#6876)
Co-authored-by: Abhijeet Prasad <[email protected]>
1 parent ca4c65e commit 6bca76e

File tree

4 files changed

+89
-8
lines changed

4 files changed

+89
-8
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable no-unused-vars */
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
includeLocalVariables: true,
7+
integrations: [new Sentry.Integrations.LocalVariables({ captureAllExceptions: true })],
8+
beforeSend: event => {
9+
// eslint-disable-next-line no-console
10+
console.log(JSON.stringify(event));
11+
},
12+
});
13+
14+
class Some {
15+
two(name) {
16+
throw new Error('Enough!');
17+
}
18+
}
19+
20+
function one(name) {
21+
const arr = [1, '2', null];
22+
const obj = {
23+
name,
24+
num: 5,
25+
};
26+
27+
const ty = new Some();
28+
29+
ty.two(name);
30+
}
31+
32+
try {
33+
one('some name');
34+
} catch (e) {
35+
Sentry.captureException(e);
36+
}

packages/node-integration-tests/suites/public-api/LocalVariables/test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,32 @@ describe('LocalVariables integration', () => {
5151
done();
5252
});
5353
});
54+
55+
test('Includes local variables for caught exceptions when enabled', done => {
56+
expect.assertions(4);
57+
58+
const testScriptPath = path.resolve(__dirname, 'local-variables-caught.js');
59+
60+
childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => {
61+
const event = JSON.parse(stdout) as Event;
62+
63+
const frames = event.exception?.values?.[0].stacktrace?.frames || [];
64+
const lastFrame = frames[frames.length - 1];
65+
66+
expect(lastFrame.function).toBe('Some.two');
67+
expect(lastFrame.vars).toEqual({ name: 'some name' });
68+
69+
const penultimateFrame = frames[frames.length - 2];
70+
71+
expect(penultimateFrame.function).toBe('one');
72+
expect(penultimateFrame.vars).toEqual({
73+
name: 'some name',
74+
arr: [1, '2', null],
75+
obj: { name: 'some name', num: 5 },
76+
ty: '<Some>',
77+
});
78+
79+
done();
80+
});
81+
});
5482
});

packages/node/src/integrations/localvariables.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import type { NodeClientOptions } from '../types';
66

77
export interface DebugSession {
88
/** Configures and connects to the debug session */
9-
configureAndConnect(onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void): void;
9+
configureAndConnect(
10+
onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void,
11+
captureAll: boolean,
12+
): void;
1013
/** Gets local variables for an objectId */
1114
getLocalVariables(objectId: string): Promise<Record<string, unknown>>;
1215
}
@@ -32,12 +35,15 @@ class AsyncSession implements DebugSession {
3235
}
3336

3437
/** @inheritdoc */
35-
public configureAndConnect(onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void): void {
38+
public configureAndConnect(
39+
onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void,
40+
captureAll: boolean,
41+
): void {
3642
this._session.connect();
3743
this._session.on('Debugger.paused', onPause);
3844
this._session.post('Debugger.enable');
3945
// We only want to pause on uncaught exceptions
40-
this._session.post('Debugger.setPauseOnExceptions', { state: 'uncaught' });
46+
this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' });
4147
}
4248

4349
/** @inheritdoc */
@@ -164,7 +170,14 @@ export interface FrameVariables {
164170

165171
/** There are no options yet. This allows them to be added later without breaking changes */
166172
// eslint-disable-next-line @typescript-eslint/no-empty-interface
167-
interface Options {}
173+
interface Options {
174+
/**
175+
* Capture local variables for both handled and unhandled exceptions
176+
*
177+
* Default: false - Only captures local variables for uncaught exceptions
178+
*/
179+
captureAllExceptions?: boolean;
180+
}
168181

169182
/**
170183
* Adds local variables to exception frames
@@ -177,7 +190,7 @@ export class LocalVariables implements Integration {
177190
private readonly _cachedFrames: LRUMap<string, Promise<FrameVariables[]>> = new LRUMap(20);
178191

179192
public constructor(
180-
_options: Options = {},
193+
private readonly _options: Options = {},
181194
private readonly _session: DebugSession | undefined = tryNewAsyncSession(),
182195
) {}
183196

@@ -194,8 +207,9 @@ export class LocalVariables implements Integration {
194207
clientOptions: NodeClientOptions | undefined,
195208
): void {
196209
if (this._session && clientOptions?.includeLocalVariables) {
197-
this._session.configureAndConnect(ev =>
198-
this._handlePaused(clientOptions.stackParser, ev as InspectorNotification<PausedExceptionEvent>),
210+
this._session.configureAndConnect(
211+
ev => this._handlePaused(clientOptions.stackParser, ev as InspectorNotification<PausedExceptionEvent>),
212+
!!this._options.captureAllExceptions,
199213
);
200214

201215
addGlobalEventProcessor(async event => this._addLocalVariables(event));

packages/node/test/integrations/localvariables.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ class MockDebugSession implements DebugSession {
1717

1818
constructor(private readonly _vars: Record<string, Record<string, unknown>>, private readonly _throwOn?: ThrowOn) {}
1919

20-
public configureAndConnect(onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void): void {
20+
public configureAndConnect(
21+
onPause: (message: InspectorNotification<Debugger.PausedEventDataType>) => void,
22+
_captureAll: boolean,
23+
): void {
2124
if (this._throwOn?.configureAndConnect) {
2225
throw new Error('configureAndConnect should not be called');
2326
}

0 commit comments

Comments
 (0)