Skip to content

Commit 682efa5

Browse files
authored
fix(replay): Ensure dropped errors are removed from replay reference (#6299)
1 parent 3f5c9c4 commit 682efa5

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

packages/replay/src/index.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-lines */ // TODO: We might want to split this file up
22
import type { BrowserClient, BrowserOptions } from '@sentry/browser';
33
import { addGlobalEventProcessor, getCurrentHub, Scope, setContext } from '@sentry/core';
4-
import { Breadcrumb, Client, Event, Integration } from '@sentry/types';
4+
import { Breadcrumb, Client, DataCategory, Event, EventDropReason, Integration } from '@sentry/types';
55
import { addInstrumentationHandler, createEnvelope, logger } from '@sentry/utils';
66
import debounce from 'lodash.debounce';
77
import { PerformanceObserverEntryList } from 'perf_hooks';
@@ -126,6 +126,11 @@ export class Replay implements Integration {
126126
*/
127127
private stopRecording: ReturnType<typeof record> | null = null;
128128

129+
/**
130+
* We overwrite `client.recordDroppedEvent`, but store the original method here so we can restore it.
131+
*/
132+
private _originalRecordDroppedEvent?: Client['recordDroppedEvent'];
133+
129134
private context: InternalEventContext = {
130135
errorIds: new Set(),
131136
traceIds: new Set(),
@@ -405,6 +410,9 @@ export class Replay implements Integration {
405410
WINDOW.addEventListener('blur', this.handleWindowBlur);
406411
WINDOW.addEventListener('focus', this.handleWindowFocus);
407412

413+
// We need to filter out dropped events captured by `addGlobalEventProcessor(this.handleGlobalEvent)` below
414+
this._overwriteRecordDroppedEvent();
415+
408416
// There is no way to remove these listeners, so ensure they are only added once
409417
if (!this.hasInitializedCoreListeners) {
410418
// Listeners from core SDK //
@@ -467,6 +475,8 @@ export class Replay implements Integration {
467475
WINDOW.removeEventListener('blur', this.handleWindowBlur);
468476
WINDOW.removeEventListener('focus', this.handleWindowFocus);
469477

478+
this._restoreRecordDroppedEvent();
479+
470480
if (this.performanceObserver) {
471481
this.performanceObserver.disconnect();
472482
this.performanceObserver = null;
@@ -1352,4 +1362,39 @@ export class Replay implements Integration {
13521362
this.options.errorSampleRate = opt.replaysOnErrorSampleRate;
13531363
}
13541364
}
1365+
1366+
private _overwriteRecordDroppedEvent(): void {
1367+
const client = getCurrentHub().getClient();
1368+
1369+
if (!client) {
1370+
return;
1371+
}
1372+
1373+
const _originalCallback = client.recordDroppedEvent.bind(client);
1374+
1375+
const recordDroppedEvent: Client['recordDroppedEvent'] = (
1376+
reason: EventDropReason,
1377+
category: DataCategory,
1378+
event?: Event,
1379+
): void => {
1380+
if (event && event.event_id) {
1381+
this.context.errorIds.delete(event.event_id);
1382+
}
1383+
1384+
return _originalCallback(reason, category, event);
1385+
};
1386+
1387+
client.recordDroppedEvent = recordDroppedEvent;
1388+
this._originalRecordDroppedEvent = _originalCallback;
1389+
}
1390+
1391+
private _restoreRecordDroppedEvent(): void {
1392+
const client = getCurrentHub().getClient();
1393+
1394+
if (!client || !this._originalRecordDroppedEvent) {
1395+
return;
1396+
}
1397+
1398+
client.recordDroppedEvent = this._originalRecordDroppedEvent;
1399+
}
13551400
}

packages/replay/test/unit/index-handleGlobalEvent.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getCurrentHub } from '@sentry/core';
12
import { Error } from '@test/fixtures/error';
23
import { Transaction } from '@test/fixtures/transaction';
34
import { resetSdkMock } from '@test/mocks';
@@ -83,6 +84,27 @@ it('only tags errors with replay id, adds trace and error id to context for erro
8384
expect(replay.waitForError).toBe(false);
8485
});
8586

87+
it('strips out dropped events from errorIds', async () => {
88+
const error1 = Error({ event_id: 'err1' });
89+
const error2 = Error({ event_id: 'err2' });
90+
const error3 = Error({ event_id: 'err3' });
91+
92+
replay['_overwriteRecordDroppedEvent']();
93+
94+
const client = getCurrentHub().getClient()!;
95+
96+
replay.handleGlobalEvent(error1);
97+
replay.handleGlobalEvent(error2);
98+
replay.handleGlobalEvent(error3);
99+
100+
client.recordDroppedEvent('before_send', 'error', { event_id: 'err2' });
101+
102+
// @ts-ignore private
103+
expect(Array.from(replay.context.errorIds)).toEqual(['err1', 'err3']);
104+
105+
replay['_restoreRecordDroppedEvent']();
106+
});
107+
86108
it('tags errors and transactions with replay id for session samples', async () => {
87109
({ replay } = await resetSdkMock({
88110
sessionSampleRate: 1.0,

0 commit comments

Comments
 (0)