Skip to content

Commit 2c4366c

Browse files
authored
feat(replay): Improve rrweb error ignoring (#7087)
Based on getsentry/rrweb#41, we can now ignore rrweb errors even when the code is minified.
1 parent b1e5c9b commit 2c4366c

File tree

3 files changed

+115
-24
lines changed

3 files changed

+115
-24
lines changed

packages/replay/src/coreHandlers/handleGlobalEvent.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { addBreadcrumb } from '@sentry/core';
2-
import type { Event } from '@sentry/types';
2+
import type { Event, EventHint } from '@sentry/types';
33
import { logger } from '@sentry/utils';
44

55
import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants';
@@ -9,8 +9,8 @@ import { isRrwebError } from '../util/isRrwebError';
99
/**
1010
* Returns a listener to be added to `addGlobalEventProcessor(listener)`.
1111
*/
12-
export function handleGlobalEventListener(replay: ReplayContainer): (event: Event) => Event | null {
13-
return (event: Event) => {
12+
export function handleGlobalEventListener(replay: ReplayContainer): (event: Event, hint: EventHint) => Event | null {
13+
return (event: Event, hint: EventHint) => {
1414
// Do not apply replayId to the root event
1515
if (event.type === REPLAY_EVENT_NAME) {
1616
// Replays have separate set of breadcrumbs, do not include breadcrumbs
@@ -21,7 +21,7 @@ export function handleGlobalEventListener(replay: ReplayContainer): (event: Even
2121

2222
// Unless `captureExceptions` is enabled, we want to ignore errors coming from rrweb
2323
// As there can be a bunch of stuff going wrong in internals there, that we don't want to bubble up to users
24-
if (isRrwebError(event) && !replay.getOptions()._experiments.captureExceptions) {
24+
if (isRrwebError(event, hint) && !replay.getOptions()._experiments.captureExceptions) {
2525
__DEBUG_BUILD__ && logger.log('[Replay] Ignoring error from rrweb internals', event);
2626
return null;
2727
}

packages/replay/src/util/isRrwebError.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import type { Event } from '@sentry/types';
1+
import type { Event, EventHint } from '@sentry/types';
22

33
/**
44
* Returns true if we think the given event is an error originating inside of rrweb.
55
*/
6-
export function isRrwebError(event: Event): boolean {
6+
export function isRrwebError(event: Event, hint: EventHint): boolean {
77
if (event.type || !event.exception || !event.exception.values || !event.exception.values.length) {
88
return false;
99
}
1010

11+
// @ts-ignore this may be set by rrweb when it finds errors
12+
if (hint.originalException && hint.originalException.__rrweb__) {
13+
return true;
14+
}
15+
1116
// Check if any exception originates from rrweb
1217
return event.exception.values.some(exception => {
1318
if (!exception.stacktrace || !exception.stacktrace.frames || !exception.stacktrace.frames.length) {

packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,32 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
4040
};
4141

4242
// @ts-ignore replay event type
43-
expect(handleGlobalEventListener(replay)(replayEvent)).toEqual({
43+
expect(handleGlobalEventListener(replay)(replayEvent, {})).toEqual({
4444
type: REPLAY_EVENT_NAME,
4545
});
4646
});
4747

4848
it('does not delete breadcrumbs from error and transaction events', () => {
4949
expect(
50-
handleGlobalEventListener(replay)({
51-
breadcrumbs: [{ type: 'fakecrumb' }],
52-
}),
50+
handleGlobalEventListener(replay)(
51+
{
52+
breadcrumbs: [{ type: 'fakecrumb' }],
53+
},
54+
{},
55+
),
5356
).toEqual(
5457
expect.objectContaining({
5558
breadcrumbs: [{ type: 'fakecrumb' }],
5659
}),
5760
);
5861
expect(
59-
handleGlobalEventListener(replay)({
60-
type: 'transaction',
61-
breadcrumbs: [{ type: 'fakecrumb' }],
62-
}),
62+
handleGlobalEventListener(replay)(
63+
{
64+
type: 'transaction',
65+
breadcrumbs: [{ type: 'fakecrumb' }],
66+
},
67+
{},
68+
),
6369
).toEqual(
6470
expect.objectContaining({
6571
breadcrumbs: [{ type: 'fakecrumb' }],
@@ -76,7 +82,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
7682
tags: expect.not.objectContaining({ replayId: expect.anything() }),
7783
}),
7884
);
79-
expect(handleGlobalEventListener(replay)(error)).toEqual(
85+
expect(handleGlobalEventListener(replay)(error, {})).toEqual(
8086
expect.objectContaining({
8187
tags: expect.objectContaining({ replayId: expect.any(String) }),
8288
}),
@@ -102,9 +108,9 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
102108

103109
const client = getCurrentHub().getClient()!;
104110

105-
handleGlobalEventListener(replay)(error1);
106-
handleGlobalEventListener(replay)(error2);
107-
handleGlobalEventListener(replay)(error3);
111+
handleGlobalEventListener(replay)(error1, {});
112+
handleGlobalEventListener(replay)(error2, {});
113+
handleGlobalEventListener(replay)(error3, {});
108114

109115
client.recordDroppedEvent('before_send', 'error', { event_id: 'err2' });
110116

@@ -125,7 +131,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
125131
tags: expect.objectContaining({ replayId: expect.any(String) }),
126132
}),
127133
);
128-
expect(handleGlobalEventListener(replay)(error)).toEqual(
134+
expect(handleGlobalEventListener(replay)(error, {})).toEqual(
129135
expect.objectContaining({
130136
tags: expect.objectContaining({ replayId: expect.any(String) }),
131137
}),
@@ -166,7 +172,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
166172
event_id: 'ff1616b1e13744c6964281349aecc82a',
167173
};
168174

169-
expect(handleGlobalEventListener(replay)(errorEvent)).toEqual(errorEvent);
175+
expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent);
170176
});
171177

172178
it('skips rrweb internal errors', () => {
@@ -204,7 +210,87 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
204210
event_id: 'ff1616b1e13744c6964281349aecc82a',
205211
};
206212

207-
expect(handleGlobalEventListener(replay)(errorEvent)).toEqual(null);
213+
expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(null);
214+
});
215+
216+
it('skips exception with __rrweb__ set', () => {
217+
const errorEvent: Event = {
218+
exception: {
219+
values: [
220+
{
221+
type: 'TypeError',
222+
value: "Cannot read properties of undefined (reading 'contains')",
223+
stacktrace: {
224+
frames: [
225+
{
226+
filename: 'scrambled.js',
227+
function: 'MutationBuffer.processMutations',
228+
in_app: true,
229+
lineno: 101,
230+
colno: 23,
231+
},
232+
{
233+
filename: '<anonymous>',
234+
function: 'Array.forEach',
235+
in_app: true,
236+
},
237+
],
238+
},
239+
mechanism: {
240+
type: 'generic',
241+
handled: true,
242+
},
243+
},
244+
],
245+
},
246+
level: 'error',
247+
event_id: 'ff1616b1e13744c6964281349aecc82a',
248+
};
249+
250+
const originalException = new window.Error('some exception');
251+
// @ts-ignore this could be set by rrweb
252+
originalException.__rrweb__ = true;
253+
254+
expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(null);
255+
});
256+
257+
it('handles string exceptions', () => {
258+
const errorEvent: Event = {
259+
exception: {
260+
values: [
261+
{
262+
type: 'TypeError',
263+
value: "Cannot read properties of undefined (reading 'contains')",
264+
stacktrace: {
265+
frames: [
266+
{
267+
filename: 'scrambled.js',
268+
function: 'MutationBuffer.processMutations',
269+
in_app: true,
270+
lineno: 101,
271+
colno: 23,
272+
},
273+
{
274+
filename: '<anonymous>',
275+
function: 'Array.forEach',
276+
in_app: true,
277+
},
278+
],
279+
},
280+
mechanism: {
281+
type: 'generic',
282+
handled: true,
283+
},
284+
},
285+
],
286+
},
287+
level: 'error',
288+
event_id: 'ff1616b1e13744c6964281349aecc82a',
289+
};
290+
291+
const originalException = 'some string exception';
292+
293+
expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(errorEvent);
208294
});
209295

210296
it('does not skip rrweb internal errors with _experiments.captureExceptions', () => {
@@ -244,7 +330,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
244330

245331
replay.getOptions()._experiments = { captureExceptions: true };
246332

247-
expect(handleGlobalEventListener(replay)(errorEvent)).toEqual(errorEvent);
333+
expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent);
248334
});
249335

250336
it('does not skip non-rrweb errors when no stacktrace exists', () => {
@@ -268,7 +354,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
268354
event_id: 'ff1616b1e13744c6964281349aecc82a',
269355
};
270356

271-
expect(handleGlobalEventListener(replay)(errorEvent)).toEqual(errorEvent);
357+
expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent);
272358
});
273359

274360
it('does not skip non-rrweb errors when no exception', () => {
@@ -278,6 +364,6 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => {
278364
event_id: 'ff1616b1e13744c6964281349aecc82a',
279365
};
280366

281-
expect(handleGlobalEventListener(replay)(errorEvent)).toEqual(errorEvent);
367+
expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent);
282368
});
283369
});

0 commit comments

Comments
 (0)