Skip to content

Commit 5f9cd66

Browse files
committed
ref(replay): Extract handleGlobalEvent handler out
1 parent 59c2d5c commit 5f9cd66

File tree

4 files changed

+89
-79
lines changed

4 files changed

+89
-79
lines changed

packages/replay/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
99
export const REPLAY_SESSION_KEY = 'sentryReplaySession';
1010
export const REPLAY_EVENT_NAME = 'replay_event';
1111
export const RECORDING_EVENT_NAME = 'replay_recording';
12+
export const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
1213

1314
// The idle limit for a session
1415
export const SESSION_IDLE_DURATION = 300_000; // 5 minutes in ms
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Event } from '@sentry/types';
2+
3+
import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants';
4+
import { ReplayContainer } from '../replay';
5+
import { addInternalBreadcrumb } from '../util/addInternalBreadcrumb';
6+
7+
export function handleGlobalEventListener(replay: ReplayContainer): (event: Event) => Event {
8+
return (event: Event) => {
9+
// Do not apply replayId to the root event
10+
if (
11+
// @ts-ignore new event type
12+
event.type === REPLAY_EVENT_NAME
13+
) {
14+
// Replays have separate set of breadcrumbs, do not include breadcrumbs
15+
// from core SDK
16+
delete event.breadcrumbs;
17+
return event;
18+
}
19+
20+
// Only tag transactions with replayId if not waiting for an error
21+
// @ts-ignore private
22+
if (event.type !== 'transaction' || !replay._waitForError) {
23+
event.tags = { ...event.tags, replayId: replay.session?.id };
24+
}
25+
26+
// Collect traceIds in _context regardless of `_waitForError` - if it's true,
27+
// _context gets cleared on every checkout
28+
if (event.type === 'transaction') {
29+
// @ts-ignore private
30+
replay._context.traceIds.add(String(event.contexts?.trace?.trace_id || ''));
31+
return event;
32+
}
33+
34+
// XXX: Is it safe to assume that all other events are error events?
35+
// @ts-ignore: Type 'undefined' is not assignable to type 'string'.ts(2345)
36+
replay._context.errorIds.add(event.event_id);
37+
38+
const exc = event.exception?.values?.[0];
39+
addInternalBreadcrumb({
40+
message: `Tagging event (${event.event_id}) - ${event.message} - ${exc?.type || 'Unknown'}: ${
41+
exc?.value || 'n/a'
42+
}`,
43+
});
44+
45+
// Need to be very careful that this does not cause an infinite loop
46+
if (
47+
// @ts-ignore private
48+
replay._waitForError &&
49+
event.exception &&
50+
event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
51+
) {
52+
setTimeout(async () => {
53+
// Allow flush to complete before resuming as a session recording, otherwise
54+
// the checkout from `startRecording` may be included in the payload.
55+
// Prefer to keep the error replay as a separate (and smaller) segment
56+
// than the session replay.
57+
await replay.flushImmediate();
58+
59+
// @ts-ignore private
60+
if (replay._stopRecording) {
61+
// @ts-ignore private
62+
replay._stopRecording();
63+
// Reset all "capture on error" configuration before
64+
// starting a new recording
65+
// @ts-ignore private
66+
replay._waitForError = false;
67+
replay.startRecording();
68+
}
69+
});
70+
}
71+
72+
return event;
73+
};
74+
}

packages/replay/src/replay.ts

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import {
1010
MAX_SESSION_LIFE,
1111
REPLAY_EVENT_NAME,
1212
SESSION_IDLE_DURATION,
13+
UNABLE_TO_SEND_REPLAY,
1314
VISIBILITY_CHANGE_TIMEOUT,
1415
WINDOW,
1516
} from './constants';
1617
import { breadcrumbHandler } from './coreHandlers/breadcrumbHandler';
1718
import { handleFetchSpanListener } from './coreHandlers/handleFetch';
19+
import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent';
1820
import { handleHistorySpanListener } from './coreHandlers/handleHistory';
1921
import { handleXhrSpanListener } from './coreHandlers/handleXhr';
2022
import { createMemoryEntry, createPerformanceEntries, ReplayPerformanceEntry } from './createPerformanceEntry';
@@ -33,7 +35,6 @@ import type {
3335
ReplayPluginOptions,
3436
SendReplay,
3537
} from './types';
36-
import { addInternalBreadcrumb } from './util/addInternalBreadcrumb';
3738
import { captureInternalException } from './util/captureInternalException';
3839
import { createBreadcrumb } from './util/createBreadcrumb';
3940
import { createPayload } from './util/createPayload';
@@ -49,7 +50,6 @@ type AddUpdateCallback = () => boolean | void;
4950

5051
const BASE_RETRY_INTERVAL = 5000;
5152
const MAX_RETRY_COUNT = 3;
52-
const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
5353

5454
export class ReplayContainer {
5555
public eventBuffer: EventBuffer | null = null;
@@ -321,7 +321,7 @@ export class ReplayContainer {
321321

322322
// Tag all (non replay) events that get sent to Sentry with the current
323323
// replay ID so that we can reference them later in the UI
324-
addGlobalEventProcessor(this.handleGlobalEvent);
324+
addGlobalEventProcessor(handleGlobalEventListener(this));
325325

326326
this._hasInitializedCoreListeners = true;
327327
}
@@ -412,72 +412,6 @@ export class ReplayContainer {
412412
this._debouncedFlush();
413413
}
414414

415-
/**
416-
* Core Sentry SDK global event handler. Attaches `replayId` to all [non-replay]
417-
* events as a tag. Also handles the case where we only want to capture a reply
418-
* when an error occurs.
419-
**/
420-
handleGlobalEvent: (event: Event) => Event = (event: Event) => {
421-
// Do not apply replayId to the root event
422-
if (
423-
// @ts-ignore new event type
424-
event.type === REPLAY_EVENT_NAME
425-
) {
426-
// Replays have separate set of breadcrumbs, do not include breadcrumbs
427-
// from core SDK
428-
delete event.breadcrumbs;
429-
return event;
430-
}
431-
432-
// Only tag transactions with replayId if not waiting for an error
433-
if (event.type !== 'transaction' || !this._waitForError) {
434-
event.tags = { ...event.tags, replayId: this.session?.id };
435-
}
436-
437-
// Collect traceIds in _context regardless of `_waitForError` - if it's true,
438-
// _context gets cleared on every checkout
439-
if (event.type === 'transaction') {
440-
this._context.traceIds.add(String(event.contexts?.trace?.trace_id || ''));
441-
return event;
442-
}
443-
444-
// XXX: Is it safe to assume that all other events are error events?
445-
// @ts-ignore: Type 'undefined' is not assignable to type 'string'.ts(2345)
446-
this._context.errorIds.add(event.event_id);
447-
448-
const exc = event.exception?.values?.[0];
449-
addInternalBreadcrumb({
450-
message: `Tagging event (${event.event_id}) - ${event.message} - ${exc?.type || 'Unknown'}: ${
451-
exc?.value || 'n/a'
452-
}`,
453-
});
454-
455-
// Need to be very careful that this does not cause an infinite loop
456-
if (
457-
this._waitForError &&
458-
event.exception &&
459-
event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
460-
) {
461-
setTimeout(async () => {
462-
// Allow flush to complete before resuming as a session recording, otherwise
463-
// the checkout from `startRecording` may be included in the payload.
464-
// Prefer to keep the error replay as a separate (and smaller) segment
465-
// than the session replay.
466-
await this.flushImmediate();
467-
468-
if (this._stopRecording) {
469-
this._stopRecording();
470-
// Reset all "capture on error" configuration before
471-
// starting a new recording
472-
this._waitForError = false;
473-
this.startRecording();
474-
}
475-
});
476-
}
477-
478-
return event;
479-
};
480-
481415
/**
482416
* Handler for recording events.
483417
*

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getCurrentHub } from '@sentry/core';
22

33
import { REPLAY_EVENT_NAME } from '../../src/constants';
4+
import { handleGlobalEventListener } from '../../src/coreHandlers/handleGlobalEvent';
45
import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from '../../src/util/monkeyPatchRecordDroppedEvent';
56
import { ReplayContainer } from './../../src/replay';
67
import { Error } from './../fixtures/error';
@@ -34,14 +35,14 @@ it('deletes breadcrumbs from replay events', () => {
3435
};
3536

3637
// @ts-ignore replay event type
37-
expect(replay.handleGlobalEvent(replayEvent)).toEqual({
38+
expect(handleGlobalEventListener(replay)(replayEvent)).toEqual({
3839
type: REPLAY_EVENT_NAME,
3940
});
4041
});
4142

4243
it('does not delete breadcrumbs from error and transaction events', () => {
4344
expect(
44-
replay.handleGlobalEvent({
45+
handleGlobalEventListener(replay)({
4546
breadcrumbs: [{ type: 'fakecrumb' }],
4647
}),
4748
).toEqual(
@@ -50,7 +51,7 @@ it('does not delete breadcrumbs from error and transaction events', () => {
5051
}),
5152
);
5253
expect(
53-
replay.handleGlobalEvent({
54+
handleGlobalEventListener(replay)({
5455
type: 'transaction',
5556
breadcrumbs: [{ type: 'fakecrumb' }],
5657
}),
@@ -65,12 +66,12 @@ it('only tags errors with replay id, adds trace and error id to context for erro
6566
const transaction = Transaction();
6667
const error = Error();
6768
// @ts-ignore idc
68-
expect(replay.handleGlobalEvent(transaction)).toEqual(
69+
expect(handleGlobalEventListener(replay)(transaction)).toEqual(
6970
expect.objectContaining({
7071
tags: expect.not.objectContaining({ replayId: expect.anything() }),
7172
}),
7273
);
73-
expect(replay.handleGlobalEvent(error)).toEqual(
74+
expect(handleGlobalEventListener(replay)(error)).toEqual(
7475
expect.objectContaining({
7576
tags: expect.objectContaining({ replayId: expect.any(String) }),
7677
}),
@@ -99,9 +100,9 @@ it('strips out dropped events from errorIds', async () => {
99100

100101
const client = getCurrentHub().getClient()!;
101102

102-
replay.handleGlobalEvent(error1);
103-
replay.handleGlobalEvent(error2);
104-
replay.handleGlobalEvent(error3);
103+
handleGlobalEventListener(replay)(error1);
104+
handleGlobalEventListener(replay)(error2);
105+
handleGlobalEventListener(replay)(error3);
105106

106107
client.recordDroppedEvent('before_send', 'error', { event_id: 'err2' });
107108

@@ -117,12 +118,12 @@ it('tags errors and transactions with replay id for session samples', async () =
117118
const transaction = Transaction();
118119
const error = Error();
119120
// @ts-ignore idc
120-
expect(replay.handleGlobalEvent(transaction)).toEqual(
121+
expect(handleGlobalEventListener(replay)(transaction)).toEqual(
121122
expect.objectContaining({
122123
tags: expect.objectContaining({ replayId: expect.any(String) }),
123124
}),
124125
);
125-
expect(replay.handleGlobalEvent(error)).toEqual(
126+
expect(handleGlobalEventListener(replay)(error)).toEqual(
126127
expect.objectContaining({
127128
tags: expect.objectContaining({ replayId: expect.any(String) }),
128129
}),

0 commit comments

Comments
 (0)