Skip to content

Commit bcd2a17

Browse files
authored
fix(node): Don't overwrite local variables for re-thrown errors (#13644)
Another way to fix this issue would be to check via the debugger if the property exists already on the error object and bail early before we have local variables. However, this would add an extra round trip call via the debugger for every error. Since re-thrown errors are far less likely, I decided instead to just not overwrite existing local variables. This PR also adds a `Debugger.resume` call in the catch case to ensure that they debugger will always resume even if we get errors while debugging. It's worth noting that this only fixes the issue in Node v20+ where we use the async debugging interface.
1 parent df79871 commit bcd2a17

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable no-unused-vars */
2+
const Sentry = require('@sentry/node');
3+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
includeLocalVariables: true,
8+
transport: loggingTransport,
9+
});
10+
11+
class Some {
12+
two(name) {
13+
throw new Error('Enough!');
14+
}
15+
}
16+
17+
function one(name) {
18+
const arr = [1, '2', null];
19+
const obj = {
20+
name,
21+
num: 5,
22+
};
23+
const bool = false;
24+
const num = 0;
25+
const str = '';
26+
const something = undefined;
27+
const somethingElse = null;
28+
29+
const ty = new Some();
30+
31+
ty.two(name);
32+
}
33+
34+
setTimeout(() => {
35+
try {
36+
try {
37+
one('some name');
38+
} catch (e) {
39+
const more = 'here';
40+
throw e;
41+
}
42+
} catch (e) {
43+
Sentry.captureException(e);
44+
}
45+
}, 1000);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ conditionalTest({ min: 18 })('LocalVariables integration', () => {
7878
});
7979
});
8080

81+
conditionalTest({ min: 20 })('Node v20+', () => {
82+
test('Should retain original local variables when error is re-thrown', done => {
83+
createRunner(__dirname, 'local-variables-rethrow.js')
84+
.expect({ event: EXPECTED_LOCAL_VARIABLES_EVENT })
85+
.start(done);
86+
});
87+
});
88+
8189
test('Includes local variables for caught exceptions when enabled', done => {
8290
createRunner(__dirname, 'local-variables-caught.js').expect({ event: EXPECTED_LOCAL_VARIABLES_EVENT }).start(done);
8391
});

packages/node/src/integrations/local-variables/worker.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ async function handlePaused(
118118
// We write the local variables to a property on the error object. These can be read by the integration as the error
119119
// event pass through the SDK event pipeline
120120
await session.post('Runtime.callFunctionOn', {
121-
functionDeclaration: `function() { this.${LOCAL_VARIABLES_KEY} = ${JSON.stringify(frames)}; }`,
121+
functionDeclaration: `function() { this.${LOCAL_VARIABLES_KEY} = this.${LOCAL_VARIABLES_KEY} || ${JSON.stringify(
122+
frames,
123+
)}; }`,
122124
silent: true,
123125
objectId,
124126
});
@@ -156,8 +158,10 @@ async function startDebugger(): Promise<void> {
156158
}, 1_000);
157159
}
158160
},
159-
_ => {
160-
// ignore any errors
161+
async _ => {
162+
if (isPaused) {
163+
await session.post('Debugger.resume');
164+
}
161165
},
162166
);
163167
});

0 commit comments

Comments
 (0)