Skip to content

Commit 33b3b23

Browse files
authored
feat: Allow capture of more than 1 ANR event (#14684)
Adds a `maxAnrEvents` option to the ANR integration which allows you to capture more than one event.
1 parent 3e9ce62 commit 33b3b23

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as assert from 'assert';
2+
import * as crypto from 'crypto';
3+
4+
import * as Sentry from '@sentry/node';
5+
6+
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
7+
8+
setTimeout(() => {
9+
process.exit();
10+
}, 10000);
11+
12+
Sentry.init({
13+
dsn: process.env.SENTRY_DSN,
14+
release: '1.0',
15+
autoSessionTracking: false,
16+
integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100, maxAnrEvents: 2 })],
17+
});
18+
19+
Sentry.setUser({ email: '[email protected]' });
20+
Sentry.addBreadcrumb({ message: 'important message!' });
21+
22+
function longWork() {
23+
for (let i = 0; i < 20; i++) {
24+
const salt = crypto.randomBytes(128).toString('base64');
25+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
26+
assert.ok(hash);
27+
}
28+
}
29+
30+
setTimeout(() => {
31+
longWork();
32+
}, 1000);
33+
34+
setTimeout(() => {
35+
longWork();
36+
}, 4000);

dev-packages/node-integration-tests/suites/anr/basic.mjs

+5
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ function longWork() {
3030
setTimeout(() => {
3131
longWork();
3232
}, 1000);
33+
34+
// Ensure we only send one event even with multiple blocking events
35+
setTimeout(() => {
36+
longWork();
37+
}, 4000);

dev-packages/node-integration-tests/suites/anr/test.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const ANR_EVENT_WITH_DEBUG_META: Event = {
101101
{
102102
type: 'sourcemap',
103103
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
104-
code_file: expect.stringContaining('basic.'),
104+
code_file: expect.stringContaining('basic'),
105105
},
106106
],
107107
},
@@ -123,6 +123,14 @@ conditionalTest({ min: 16 })('should report ANR when event loop blocked', () =>
123123
.start(done);
124124
});
125125

126+
test('multiple events via maxAnrEvents', done => {
127+
createRunner(__dirname, 'basic-multiple.mjs')
128+
.withMockSentryServer()
129+
.expect({ event: ANR_EVENT_WITH_DEBUG_META })
130+
.expect({ event: ANR_EVENT_WITH_DEBUG_META })
131+
.start(done);
132+
});
133+
126134
test('blocked indefinitely', done => {
127135
createRunner(__dirname, 'indefinite.mjs').withMockSentryServer().expect({ event: ANR_EVENT }).start(done);
128136
});

packages/node/src/integrations/anr/common.ts

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export interface AnrIntegrationOptions {
2121
* This uses the node debugger which enables the inspector API and opens the required ports.
2222
*/
2323
captureStackTrace: boolean;
24+
/**
25+
* Maximum number of ANR events to send.
26+
*
27+
* Defaults to 1.
28+
*/
29+
maxAnrEvents: number;
2430
/**
2531
* Tags to include with ANR events.
2632
*/

packages/node/src/integrations/anr/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ async function _startWorker(
160160
pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
161161
anrThreshold: integrationOptions.anrThreshold || DEFAULT_HANG_THRESHOLD,
162162
captureStackTrace: !!integrationOptions.captureStackTrace,
163+
maxAnrEvents: integrationOptions.maxAnrEvents || 1,
163164
staticTags: integrationOptions.staticTags || {},
164165
contexts,
165166
};

packages/node/src/integrations/anr/worker.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type VoidFunction = () => void;
2323

2424
const options: WorkerStartData = workerData;
2525
let session: Session | undefined;
26-
let hasSentAnrEvent = false;
26+
let sentAnrEvents = 0;
2727
let mainDebugImages: Record<string, string> = {};
2828

2929
function log(msg: string): void {
@@ -136,11 +136,11 @@ function applyScopeToEvent(event: Event, scope: ScopeData): void {
136136
}
137137

138138
async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise<void> {
139-
if (hasSentAnrEvent) {
139+
if (sentAnrEvents >= options.maxAnrEvents) {
140140
return;
141141
}
142142

143-
hasSentAnrEvent = true;
143+
sentAnrEvents += 1;
144144

145145
await sendAbnormalSession();
146146

@@ -181,11 +181,13 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise<v
181181
await transport.send(envelope);
182182
await transport.flush(2000);
183183

184-
// Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
185-
// This is mainly for the benefit of logging or debugging.
186-
setTimeout(() => {
187-
process.exit(0);
188-
}, 5_000);
184+
if (sentAnrEvents >= options.maxAnrEvents) {
185+
// Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
186+
// This is mainly for the benefit of logging or debugging.
187+
setTimeout(() => {
188+
process.exit(0);
189+
}, 5_000);
190+
}
189191
}
190192

191193
let debuggerPause: VoidFunction | undefined;

0 commit comments

Comments
 (0)