Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(replay): Add experimental option to allow for a checkout every 6 minutes #13069

Merged
merged 5 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/replay-internal/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,19 @@ export class ReplayContainer implements ReplayContainerInterface {
// When running in error sampling mode, we need to overwrite `checkoutEveryNms`
// Without this, it would record forever, until an error happens, which we don't want
// instead, we'll always keep the last 60 seconds of replay before an error happened
...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
...(this.recordingMode === 'buffer'
? { checkoutEveryNms: BUFFER_CHECKOUT_TIME }
: // Otherwise, use experimental option w/ min checkout time of 6 minutes
// This is to improve playback seeking as there could potentially be
// less mutations to process in the worse cases.
//
// checkout by "N" events is probably ideal, but means we have less
// control about the number of checkouts we make (which generally
// increases replay size)
this._options._experiments.continuousCheckout && {
// Minimum checkout time is 6 minutes
checkoutEveryNms: Math.max(360_000, this._options._experiments.continuousCheckout),
}),
emit: getHandleRecordingEmit(this),
onMutation: this._onMutationHandler,
...(canvasOptions
Expand Down
1 change: 1 addition & 0 deletions packages/replay-internal/src/types/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
_experiments: Partial<{
captureExceptions: boolean;
traceInternals: boolean;
continuousCheckout: number;
}>;
}

Expand Down
15 changes: 10 additions & 5 deletions packages/replay-internal/src/util/handleRecordingEmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
return false;
}

const session = replay.session;

// Additionally, create a meta event that will capture certain SDK settings.
// In order to handle buffer mode, this needs to either be done when we
// receive checkout events or at flush time.
// receive checkout events or at flush time. We have an experimental mode
// to perform multiple checkouts a session (the idea is to improve
// seeking during playback), so also only include if segmentId is 0
// (handled in `addSettingsEvent`).
//
// `isCheckout` is always true, but want to be explicit that it should
// only be added for checkouts
Expand All @@ -72,22 +77,22 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
// of the previous session. Do not immediately flush in this case
// to avoid capturing only the checkout and instead the replay will
// be captured if they perform any follow-up actions.
if (replay.session && replay.session.previousSessionId) {
if (session && session.previousSessionId) {
return true;
}

// When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
// this should usually be the timestamp of the checkout event, but to be safe...
if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
if (replay.recordingMode === 'buffer' && session && replay.eventBuffer) {
const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
if (earliestEvent) {
DEBUG_BUILD &&
logger.info(`Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`);

replay.session.started = earliestEvent;
session.started = earliestEvent;

if (replay.getOptions().stickySession) {
saveSession(replay.session);
saveSession(session);
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions packages/replay-internal/test/integration/rrweb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,44 @@ describe('Integration | rrweb', () => {
}
`);
});

it('calls rrweb.record with checkoutEveryNms', async () => {
const { mockRecord } = await resetSdkMock({
replayOptions: {
_experiments: {
continuousCheckout: 1,
},
},
sentryOptions: {
replaysOnErrorSampleRate: 0.0,
replaysSessionSampleRate: 1.0,
},
});

expect(mockRecord.mock.calls[0]?.[0]).toMatchInlineSnapshot(`
{
"blockSelector": ".sentry-block,[data-sentry-block],base[href="/"],img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]",
"checkoutEveryNms": 360000,
"collectFonts": true,
"emit": [Function],
"errorHandler": [Function],
"ignoreSelector": ".sentry-ignore,[data-sentry-ignore],input[type="file"]",
"inlineImages": false,
"inlineStylesheet": true,
"maskAllInputs": true,
"maskAllText": true,
"maskAttributeFn": [Function],
"maskInputFn": undefined,
"maskInputOptions": {
"password": true,
},
"maskTextFn": undefined,
"maskTextSelector": ".sentry-mask,[data-sentry-mask]",
"onMutation": [Function],
"slimDOMOptions": "all",
"unblockSelector": "",
"unmaskTextSelector": "",
}
`);
});
});
Loading