Skip to content

Commit 15ec85b

Browse files
authored
ref(replay): Log warning if sample rates are all undefined (#6959)
Since we decided to set both replay sample rates to 0 by default, we should log a warning that replay will be disabled if no sample rates were passed to Sentry.init. Since we're still supporting the deprecated sample rates in the integration options, this warning will only be emitted if these rates aren't set either.
1 parent e4e7e2e commit 15ec85b

File tree

2 files changed

+77
-21
lines changed

2 files changed

+77
-21
lines changed

packages/replay/src/integration.ts

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getCurrentHub } from '@sentry/core';
22
import type { BrowserClientReplayOptions, Integration } from '@sentry/types';
3+
import { dropUndefinedKeys } from '@sentry/utils';
34

45
import { DEFAULT_FLUSH_MAX_DELAY, DEFAULT_FLUSH_MIN_DELAY, MASK_ALL_TEXT_SELECTOR } from './constants';
56
import { ReplayContainer } from './replay';
@@ -11,6 +12,9 @@ const MEDIA_SELECTORS = 'img,image,svg,path,rect,area,video,object,picture,embed
1112

1213
let _initialized = false;
1314

15+
type InitialReplayPluginOptions = Omit<ReplayPluginOptions, 'sessionSampleRate' | 'errorSampleRate'> &
16+
Partial<Pick<ReplayPluginOptions, 'sessionSampleRate' | 'errorSampleRate'>>;
17+
1418
/**
1519
* The main replay integration class, to be passed to `init({ integrations: [] })`.
1620
*/
@@ -30,7 +34,14 @@ export class Replay implements Integration {
3034
*/
3135
private readonly _recordingOptions: RecordingOptions;
3236

33-
private readonly _options: ReplayPluginOptions;
37+
/**
38+
* Initial options passed to the replay integration, merged with default values.
39+
* Note: `sessionSampleRate` and `errorSampleRate` are not required here, as they
40+
* can only be finally set when setupOnce() is called.
41+
*
42+
* @private
43+
*/
44+
private readonly _initialOptions: InitialReplayPluginOptions;
3445

3546
private _replay?: ReplayContainer;
3647

@@ -92,12 +103,12 @@ export class Replay implements Integration {
92103
collectFonts: true,
93104
};
94105

95-
this._options = {
106+
this._initialOptions = {
96107
flushMinDelay,
97108
flushMaxDelay,
98109
stickySession,
99-
sessionSampleRate: 0,
100-
errorSampleRate: 0,
110+
sessionSampleRate,
111+
errorSampleRate,
101112
useCompression,
102113
maskAllText: typeof maskAllText === 'boolean' ? maskAllText : !maskTextSelector,
103114
blockAllMedia,
@@ -113,7 +124,7 @@ Instead, configure \`replaysSessionSampleRate\` directly in the SDK init options
113124
Sentry.init({ replaysSessionSampleRate: ${sessionSampleRate} })`,
114125
);
115126

116-
this._options.sessionSampleRate = sessionSampleRate;
127+
this._initialOptions.sessionSampleRate = sessionSampleRate;
117128
}
118129

119130
if (typeof errorSampleRate === 'number') {
@@ -125,17 +136,17 @@ Instead, configure \`replaysOnErrorSampleRate\` directly in the SDK init options
125136
Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
126137
);
127138

128-
this._options.errorSampleRate = errorSampleRate;
139+
this._initialOptions.errorSampleRate = errorSampleRate;
129140
}
130141

131-
if (this._options.maskAllText) {
142+
if (this._initialOptions.maskAllText) {
132143
// `maskAllText` is a more user friendly option to configure
133144
// `maskTextSelector`. This means that all nodes will have their text
134145
// content masked.
135146
this._recordingOptions.maskTextSelector = MASK_ALL_TEXT_SELECTOR;
136147
}
137148

138-
if (this._options.blockAllMedia) {
149+
if (this._initialOptions.blockAllMedia) {
139150
// `blockAllMedia` is a more user friendly option to configure blocking
140151
// embedded media elements
141152
this._recordingOptions.blockSelector = !this._recordingOptions.blockSelector
@@ -221,25 +232,47 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
221232
/** Setup the integration. */
222233
private _setup(): void {
223234
// Client is not available in constructor, so we need to wait until setupOnce
224-
this._loadReplayOptionsFromClient();
235+
const finalOptions = loadReplayOptionsFromClient(this._initialOptions);
225236

226237
this._replay = new ReplayContainer({
227-
options: this._options,
238+
options: finalOptions,
228239
recordingOptions: this._recordingOptions,
229240
});
230241
}
242+
}
231243

232-
/** Parse Replay-related options from SDK options */
233-
private _loadReplayOptionsFromClient(): void {
234-
const client = getCurrentHub().getClient();
235-
const opt = client && (client.getOptions() as BrowserClientReplayOptions | undefined);
244+
/** Parse Replay-related options from SDK options */
245+
function loadReplayOptionsFromClient(initialOptions: InitialReplayPluginOptions): ReplayPluginOptions {
246+
const client = getCurrentHub().getClient();
247+
const opt = client && (client.getOptions() as BrowserClientReplayOptions);
236248

237-
if (opt && typeof opt.replaysSessionSampleRate === 'number') {
238-
this._options.sessionSampleRate = opt.replaysSessionSampleRate;
239-
}
249+
const finalOptions = { sessionSampleRate: 0, errorSampleRate: 0, ...dropUndefinedKeys(initialOptions) };
240250

241-
if (opt && typeof opt.replaysOnErrorSampleRate === 'number') {
242-
this._options.errorSampleRate = opt.replaysOnErrorSampleRate;
243-
}
251+
if (!opt) {
252+
// eslint-disable-next-line no-console
253+
console.warn('SDK client is not available.');
254+
return finalOptions;
244255
}
256+
257+
if (
258+
initialOptions.sessionSampleRate == null && // TODO remove once deprecated rates are removed
259+
initialOptions.errorSampleRate == null && // TODO remove once deprecated rates are removed
260+
opt.replaysSessionSampleRate == null &&
261+
opt.replaysOnErrorSampleRate == null
262+
) {
263+
// eslint-disable-next-line no-console
264+
console.warn(
265+
'Replay is disabled because neither `replaysSessionSampleRate` nor `replaysOnErrorSampleRate` are set.',
266+
);
267+
}
268+
269+
if (typeof opt.replaysSessionSampleRate === 'number') {
270+
finalOptions.sessionSampleRate = opt.replaysSessionSampleRate;
271+
}
272+
273+
if (typeof opt.replaysOnErrorSampleRate === 'number') {
274+
finalOptions.errorSampleRate = opt.replaysOnErrorSampleRate;
275+
}
276+
277+
return finalOptions;
245278
}

packages/replay/test/integration/integrationSettings.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Integration | integrationSettings', () => {
5353
expect(mockConsole).toBeCalledTimes(1);
5454
});
5555

56-
it('works with defining 0 in integration', async () => {
56+
it('works with defining 0 in integration but logs warnings', async () => {
5757
const { replay } = await mockSdk({
5858
replayOptions: { sessionSampleRate: 0 },
5959
sentryOptions: { replaysSessionSampleRate: undefined },
@@ -164,6 +164,29 @@ describe('Integration | integrationSettings', () => {
164164
});
165165
});
166166

167+
describe('all sample rates', () => {
168+
let mockConsole: jest.SpyInstance<void>;
169+
170+
beforeEach(() => {
171+
mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn());
172+
});
173+
174+
afterEach(() => {
175+
mockConsole.mockRestore();
176+
});
177+
178+
it('logs warning if no sample rates are set', async () => {
179+
const { replay } = await mockSdk({
180+
sentryOptions: { replaysOnErrorSampleRate: undefined, replaysSessionSampleRate: undefined },
181+
replayOptions: {},
182+
});
183+
184+
expect(replay.getOptions().sessionSampleRate).toBe(0);
185+
expect(replay.getOptions().errorSampleRate).toBe(0);
186+
expect(mockConsole).toBeCalledTimes(1);
187+
});
188+
});
189+
167190
describe('maskAllText', () => {
168191
it('works with default value', async () => {
169192
const { replay } = await mockSdk({ replayOptions: {} });

0 commit comments

Comments
 (0)