Skip to content

feat(replay): Do not capture replays < 5 seconds #7949

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

Merged
merged 6 commits into from
May 16, 2023
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
32 changes: 19 additions & 13 deletions packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,17 @@ export class ReplayContainer implements ReplayContainerInterface {
}

/**
*
* Only flush if `this.recordingMode === 'session'`
*/
public conditionalFlush(): Promise<void> {
if (this.recordingMode === 'buffer') {
return Promise.resolve();
}

return this.flushImmediate();
}

/**
* Always flush via `_debouncedFlush` so that we do not have flushes triggered
* from calling both `flush` and `_debouncedFlush`. Otherwise, there could be
* cases of mulitple flushes happening closely together.
Expand All @@ -474,6 +484,13 @@ export class ReplayContainer implements ReplayContainerInterface {
return this._debouncedFlush.flush() as Promise<void>;
}

/**
* Cancels queued up flushes.
*/
public cancelFlush(): void {
this._debouncedFlush.cancel();
}

/** Get the current sesion (=replay) ID */
public getSessionId(): string | undefined {
return this.session && this.session.id;
Expand Down Expand Up @@ -723,7 +740,7 @@ export class ReplayContainer implements ReplayContainerInterface {
// Send replay when the page/tab becomes hidden. There is no reason to send
// replay if it becomes visible, since no actions we care about were done
// while it was hidden
this._conditionalFlush();
void this.conditionalFlush();
}

/**
Expand Down Expand Up @@ -807,17 +824,6 @@ export class ReplayContainer implements ReplayContainerInterface {
return Promise.all(createPerformanceSpans(this, createPerformanceEntries(entries)));
}

/**
* Only flush if `this.recordingMode === 'session'`
*/
private _conditionalFlush(): void {
if (this.recordingMode === 'buffer') {
return;
}

void this.flushImmediate();
}

/**
* Clear _context
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/replay/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
scrollTimeout: number;
ignoreSelectors: string[];
};
delayFlushOnCheckout: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: As long as we keep this an experiment, I'm fine with this name but if we make part of the public stable API, should we rename it to something less technical like initialFlushDelay?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, as stated in a diff comment, this may not be a public option, but if it is, we will rename it!

}>;
}

Expand Down Expand Up @@ -515,7 +516,9 @@ export interface ReplayContainer {
startRecording(): void;
stopRecording(): boolean;
sendBufferedReplayOrFlush(options?: SendBufferedReplayOptions): Promise<void>;
conditionalFlush(): Promise<void>;
flushImmediate(): Promise<void>;
cancelFlush(): void;
triggerUserActivity(): void;
addUpdate(cb: AddUpdateCallback): void;
getOptions(): ReplayPluginOptions;
Expand Down
24 changes: 24 additions & 0 deletions packages/replay/src/util/handleRecordingEmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,30 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
}
}

const options = replay.getOptions();

// TODO: We want this as an experiment so that we can test
// internally and create metrics before making this the default
if (options._experiments.delayFlushOnCheckout) {
// If the full snapshot is due to an initial load, we will not have
// a previous session ID. In this case, we want to buffer events
// for a set amount of time before flushing. This can help avoid
// capturing replays of users that immediately close the window.
setTimeout(() => replay.conditionalFlush(), options._experiments.delayFlushOnCheckout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is worth the overhead, to have a separate initial flush timeout 🤔 If we "enforce" this to be === general flush debounce timeout, we could simply call debounced flush and call it a day? Then it would also ensure we do not flush twice etc. out of the box?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine for now since this is an experiment. We can think of bundling it up with the debounced flush when we GA this. The conditionalFlush here operates on the debounced flush anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we allowed the general debounce timeout to be user-controlled, chances are that they set it to something too high that would cause payloads to become too big?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I don't think this is necessarily an option we expose at all - we just want to verify data integrity on Sentry before moving forward with a change.


// Cancel any previously debounced flushes to ensure there are no [near]
// simultaneous flushes happening. The latter request should be
// insignificant in this case, so wait for additional user interaction to
// trigger a new flush.
//
// This can happen because there's no guarantee that a recording event
// happens first. e.g. a mouse click can happen and trigger a debounced
// flush before the checkout.
replay.cancelFlush();

return true;
}

// Flush immediately so that we do not miss the first segment, otherwise
// it can prevent loading on the UI. This will cause an increase in short
// replays (e.g. opening and closing a tab quickly), but these can be
Expand Down
Loading