Skip to content

Commit ec0276e

Browse files
committed
ref(replay): Extract sample decision for session
1 parent 7289d9a commit ec0276e

File tree

8 files changed

+100
-96
lines changed

8 files changed

+100
-96
lines changed

Diff for: packages/replay/src/session/Session.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { uuid4 } from '@sentry/utils';
22

3-
import { SampleRates } from '../types';
43
import { isSampled } from '../util/isSampled';
54

65
type Sampled = false | 'session' | 'error';
@@ -38,13 +37,13 @@ export interface Session {
3837
/**
3938
* Get a session with defaults & applied sampling.
4039
*/
41-
export function makeSession(session: Partial<Session>, { sessionSampleRate, errorSampleRate }: SampleRates): Session {
40+
export function makeSession(session: Partial<Session> & { sampled: Sampled }): Session {
4241
const now = new Date().getTime();
4342
const id = session.id || uuid4();
4443
const started = session.started || now;
4544
const lastActivity = session.lastActivity || now;
4645
const segmentId = session.segmentId || 0;
47-
const sampled = sampleSession(session.sampled, { sessionSampleRate, errorSampleRate });
46+
const sampled = session.sampled;
4847

4948
return {
5049
id,
@@ -55,7 +54,14 @@ export function makeSession(session: Partial<Session>, { sessionSampleRate, erro
5554
};
5655
}
5756

58-
function sampleSession(sampled: Sampled | undefined, { sessionSampleRate, errorSampleRate }: SampleRates): Sampled {
57+
/**
58+
* Get the sampled status for a session based on sample rates & current sampled status.
59+
*/
60+
export function sampleSession(
61+
sampled: Sampled | undefined,
62+
sessionSampleRate: number,
63+
errorSampleRate: number,
64+
): Sampled {
5965
if (typeof sampled !== 'undefined') {
6066
return sampled;
6167
}

Diff for: packages/replay/src/session/createSession.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@ import { logger } from '@sentry/utils';
22

33
import { SessionOptions } from '../types';
44
import { saveSession } from './saveSession';
5-
import { makeSession, Session } from './Session';
5+
import { makeSession, sampleSession, Session } from './Session';
66

77
/**
88
* Create a new session, which in its current implementation is a Sentry event
99
* that all replays will be saved to as attachments. Currently, we only expect
1010
* one of these Sentry events per "replay session".
1111
*/
1212
export function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }: SessionOptions): Session {
13-
const session = makeSession(
14-
{},
15-
{
16-
errorSampleRate,
17-
sessionSampleRate,
18-
},
19-
);
13+
const sampled = sampleSession(undefined, sessionSampleRate, errorSampleRate);
14+
const session = makeSession({
15+
sampled,
16+
});
2017

2118
__DEBUG_BUILD__ && logger.log(`[Replay] Creating new session: ${session.id}`);
2219

Diff for: packages/replay/src/session/fetchSession.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { REPLAY_SESSION_KEY, WINDOW } from '../constants';
22
import { SampleRates } from '../types';
3-
import { makeSession, Session } from './Session';
3+
import { makeSession, sampleSession, Session } from './Session';
44

55
/**
66
* Fetches a session from storage
@@ -20,9 +20,10 @@ export function fetchSession({ sessionSampleRate, errorSampleRate }: SampleRates
2020
return null;
2121
}
2222

23-
const sessionObj = JSON.parse(sessionStringFromStorage);
23+
const sessionObj = JSON.parse(sessionStringFromStorage) as Partial<Session>;
24+
const sampled = sampleSession(sessionObj.sampled, sessionSampleRate, errorSampleRate);
2425

25-
return makeSession(sessionObj, { sessionSampleRate, errorSampleRate });
26+
return makeSession({ ...sessionObj, sampled });
2627
} catch {
2728
return null;
2829
}

Diff for: packages/replay/test/unit/session/Session.test.ts

+13-31
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jest.mock('@sentry/utils', () => {
2626
import * as Sentry from '@sentry/browser';
2727

2828
import { WINDOW } from '../../../src/constants';
29-
import { makeSession } from '../../../src/session/Session';
29+
import { makeSession, sampleSession } from '../../../src/session/Session';
3030

3131
type CaptureEventMockType = jest.MockedFunction<typeof Sentry.captureEvent>;
3232

@@ -39,51 +39,33 @@ afterEach(() => {
3939
});
4040

4141
it('does not sample', function () {
42-
const newSession = makeSession(
43-
{},
44-
{
45-
sessionSampleRate: 0.0,
46-
errorSampleRate: 0.0,
47-
},
48-
);
42+
const newSession = makeSession({
43+
sampled: sampleSession(undefined, 0, 0),
44+
});
4945

5046
expect(newSession.sampled).toBe(false);
5147
});
5248

5349
it('samples using `sessionSampleRate`', function () {
54-
const newSession = makeSession(
55-
{},
56-
{
57-
sessionSampleRate: 1.0,
58-
errorSampleRate: 0.0,
59-
},
60-
);
50+
const newSession = makeSession({
51+
sampled: sampleSession(undefined, 1.0, 0),
52+
});
6153

6254
expect(newSession.sampled).toBe('session');
6355
});
6456

6557
it('samples using `errorSampleRate`', function () {
66-
const newSession = makeSession(
67-
{},
68-
{
69-
sessionSampleRate: 0,
70-
errorSampleRate: 1.0,
71-
},
72-
);
58+
const newSession = makeSession({
59+
sampled: sampleSession(undefined, 0, 1),
60+
});
7361

7462
expect(newSession.sampled).toBe('error');
7563
});
7664

7765
it('does not run sampling function if existing session was sampled', function () {
78-
const newSession = makeSession(
79-
{
80-
sampled: 'session',
81-
},
82-
{
83-
sessionSampleRate: 0,
84-
errorSampleRate: 0,
85-
},
86-
);
66+
const newSession = makeSession({
67+
sampled: 'session',
68+
});
8769

8870
expect(newSession.sampled).toBe('session');
8971
});

Diff for: packages/replay/test/unit/session/fetchSession.test.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,44 @@ const SAMPLE_RATES = {
2323
it('fetches a valid and sampled session', function () {
2424
WINDOW.sessionStorage.setItem(
2525
REPLAY_SESSION_KEY,
26-
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": true,"started":1648827162630,"lastActivity":1648827162658}',
26+
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": "session","started":1648827162630,"lastActivity":1648827162658}',
2727
);
2828

2929
expect(fetchSession(SAMPLE_RATES)).toEqual({
3030
id: 'fd09adfc4117477abc8de643e5a5798a',
3131
lastActivity: 1648827162658,
3232
segmentId: 0,
33-
sampled: true,
33+
sampled: 'session',
34+
started: 1648827162630,
35+
});
36+
});
37+
38+
it('fetches an unsampled session', function () {
39+
WINDOW.sessionStorage.setItem(
40+
REPLAY_SESSION_KEY,
41+
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": false,"started":1648827162630,"lastActivity":1648827162658}',
42+
);
43+
44+
expect(fetchSession(SAMPLE_RATES)).toEqual({
45+
id: 'fd09adfc4117477abc8de643e5a5798a',
46+
lastActivity: 1648827162658,
47+
segmentId: 0,
48+
sampled: false,
49+
started: 1648827162630,
50+
});
51+
});
52+
53+
it('auto-fixes a session without sampled', function () {
54+
WINDOW.sessionStorage.setItem(
55+
REPLAY_SESSION_KEY,
56+
'{"id":"fd09adfc4117477abc8de643e5a5798a","started":1648827162630,"lastActivity":1648827162658}',
57+
);
58+
59+
expect(fetchSession(SAMPLE_RATES)).toEqual({
60+
id: 'fd09adfc4117477abc8de643e5a5798a',
61+
lastActivity: 1648827162658,
62+
segmentId: 0,
63+
sampled: 'session',
3464
started: 1648827162630,
3565
});
3666
});

Diff for: packages/replay/test/unit/session/getSession.test.ts

+21-28
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ const SAMPLE_RATES = {
1818
};
1919

2020
function createMockSession(when: number = new Date().getTime()) {
21-
return makeSession(
22-
{
23-
id: 'test_session_id',
24-
segmentId: 0,
25-
lastActivity: when,
26-
started: when,
27-
sampled: 'session',
28-
},
29-
{ ...SAMPLE_RATES },
30-
);
21+
return makeSession({
22+
id: 'test_session_id',
23+
segmentId: 0,
24+
lastActivity: when,
25+
started: when,
26+
sampled: 'session',
27+
});
3128
}
3229

3330
beforeAll(() => {
@@ -84,15 +81,13 @@ it('creates a non-sticky session, when one is expired', function () {
8481
expiry: 1000,
8582
stickySession: false,
8683
...SAMPLE_RATES,
87-
currentSession: makeSession(
88-
{
89-
id: 'old_session_id',
90-
lastActivity: new Date().getTime() - 1001,
91-
started: new Date().getTime() - 1001,
92-
segmentId: 0,
93-
},
94-
{ ...SAMPLE_RATES },
95-
),
84+
currentSession: makeSession({
85+
id: 'old_session_id',
86+
lastActivity: new Date().getTime() - 1001,
87+
started: new Date().getTime() - 1001,
88+
segmentId: 0,
89+
sampled: 'session',
90+
}),
9691
});
9792

9893
expect(FetchSession.fetchSession).not.toHaveBeenCalled();
@@ -180,15 +175,13 @@ it('fetches a non-expired non-sticky session', function () {
180175
expiry: 1000,
181176
stickySession: false,
182177
...SAMPLE_RATES,
183-
currentSession: makeSession(
184-
{
185-
id: 'test_session_id_2',
186-
lastActivity: +new Date() - 500,
187-
started: +new Date() - 500,
188-
segmentId: 0,
189-
},
190-
{ ...SAMPLE_RATES },
191-
),
178+
currentSession: makeSession({
179+
id: 'test_session_id_2',
180+
lastActivity: +new Date() - 500,
181+
started: +new Date() - 500,
182+
segmentId: 0,
183+
sampled: 'session',
184+
}),
192185
});
193186

194187
expect(FetchSession.fetchSession).not.toHaveBeenCalled();

Diff for: packages/replay/test/unit/session/saveSession.test.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ afterEach(() => {
1111
});
1212

1313
it('saves a valid session', function () {
14-
const session = makeSession(
15-
{
16-
id: 'fd09adfc4117477abc8de643e5a5798a',
17-
segmentId: 0,
18-
started: 1648827162630,
19-
lastActivity: 1648827162658,
20-
sampled: 'session',
21-
},
22-
{ sessionSampleRate: 1.0, errorSampleRate: 0 },
23-
);
14+
const session = makeSession({
15+
id: 'fd09adfc4117477abc8de643e5a5798a',
16+
segmentId: 0,
17+
started: 1648827162630,
18+
lastActivity: 1648827162658,
19+
sampled: 'session',
20+
});
2421
saveSession(session);
2522

2623
expect(WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY)).toEqual(JSON.stringify(session));

Diff for: packages/replay/test/unit/util/isSessionExpired.test.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ import { makeSession } from '../../../src/session/Session';
22
import { isSessionExpired } from '../../../src/util/isSessionExpired';
33

44
function createSession(extra?: Record<string, any>) {
5-
return makeSession(
6-
{
7-
// Setting started/lastActivity to 0 makes it use the default, which is `Date.now()`
8-
started: 1,
9-
lastActivity: 1,
10-
segmentId: 0,
11-
...extra,
12-
},
13-
{ sessionSampleRate: 1.0, errorSampleRate: 0 },
14-
);
5+
return makeSession({
6+
// Setting started/lastActivity to 0 makes it use the default, which is `Date.now()`
7+
started: 1,
8+
lastActivity: 1,
9+
segmentId: 0,
10+
sampled: 'session',
11+
...extra,
12+
});
1513
}
1614

1715
it('session last activity is older than expiry time', function () {

0 commit comments

Comments
 (0)