Skip to content
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
60 changes: 28 additions & 32 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,36 @@

## Unreleased

- ✨ Three changes published from the console now end the running session, so they reach the
visitor at their next interaction instead of waiting for that session to end on its own: a
session sample rate of 0 while the visitor is being collected — the emergency stop — a rate of
100 while they are not, and a stricter Session Replay privacy level while they are being
collected. The session that ends is collected to its end as it began, so no recording is left
masked in one half and plain in the other. Every other change — any rate between 0 and 100, a
loosening privacy level, the replay and trace rates — still waits for the next session. Custom
values wait on their own too, but not once `beforeSampling` turns them into one of the three: a
callback answering 0 for the values just published ends the session exactly as a published 0
would. Nothing here happens without `remoteConfigurationEnabled: true`.
- 📝 What you will see on the day you publish one of those three: session counts rise and average
session length drops, because each affected visitor's running session is split at that moment; a
replay in progress ends at the split, and the session that follows draws again, so it carries a
new recording only if that draw keeps one; a rate of 100 makes previously invisible visitors
appear within hours rather than the next day, so collected volume climbs the same day. That is
the change taking effect, not a defect.
- 📝 "At once" means "as soon as this client hears of the change". Settings are fetched at page load
and at each new session, never on a timer, so a page nobody reloads hears of a publish at its next
session boundary — at most four hours away, the cap on a session's life. Opening a tab or
reloading any page fetches immediately and ends the session every tab shares, which is why a
visitor who touches the site converges in seconds. A change that is not one of the three still
takes effect one session after that.
- 📝 The three act on what actually changed, not on the activation mode recorded with the publish:
a change the console files as "next session" still ends the running session if it is one of them.
- ✨ Two changes published from the console now end the running session, so they reach the visitor
at their next interaction instead of waiting for that session to end on its own: a stricter
Session Replay privacy level, and a session sample rate of 0 — the emergency stop, which took up
to four hours to stop anything before this. Both apply only while the visitor is being collected;
one who is not records nothing and sends nothing, so neither change has anything to act on there.
The session that ends is collected to its end as it began, so no recording is left masked in one
half and plain in the other. Every other change still waits for the next session, including a
loosening privacy level and a rate rising to 100 — for "collect this visitor now" there is
`setForcedSession()`. Custom values wait on their own too, but not once `beforeSampling` turns
them into a rate of 0. Nothing here happens without `remoteConfigurationEnabled: true`.
- 📝 How soon "does not wait" is depends on when this client next hears of the change, and it hears
only at page load and at each new session — there is no timer. A visitor who keeps loading pages
hears within seconds of the publish and their session ends there. A single tab that is never
reloaded hears nothing until its session reaches the four-hour cap, so an always-on screen is the
case this does least for; any other tab the same visitor loads ends the session they share.
- 📝 What you will see on the day you publish one of the two: session counts rise and average
session length drops, because each affected visitor's running session is split at that moment,
and a replay in progress ends at the split — the session that follows draws again, so it carries
a new recording only if that draw keeps one. That is the change taking effect, not a defect.
- 📝 The two act on what actually changed, not on the activation mode recorded with the publish: a
change the console files as "next session" still ends the running session if it is one of them.
- 📝 `beforeSampling` is now also consulted when settings arrive, away from any draw, to work out
which rate would apply. It must stay free of side effects and answer the same way for the same
input: a callback that draws its own lottery — answering 0 or 100 at random — can end a session
that a steady answer would have left running.
- 📝 A session forced with `setForcedSession()` is not ended by a rate while it is being collected:
forcing decides whether this visitor is collected, and every draw the page makes is collected
whatever the console says, so ending it would only produce the same session again. A
stricter privacy level still ends it, because forcing says nothing about how much of the page may
be uploaded in the clear. The page forces the next session on its own, so the visit continues as
two sessions.
input: a callback that draws its own lottery — answering 0 at random — can end a session that a
steady answer would have left running.
- 📝 A session forced with `setForcedSession()` is not ended by a rate: forcing decides whether this
visitor is collected, and every draw the page makes is collected whatever the console says, so
ending it would only produce the same session again. A stricter privacy level still ends it,
because forcing says nothing about how much of the page may be uploaded in the clear. The page
forces the next session on its own, so the visit continues as two sessions.
- 📝 Turning remote configuration off is itself a change: the rates go back to the ones passed to
`init`. On a site whose init rate is 0, switching it off stops collection at once rather than at
the next session.
Expand Down
22 changes: 13 additions & 9 deletions packages/rum-core/src/domain/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,19 @@ export interface RumInitConfiguration extends InitConfiguration {
* values passed here, so they can be changed without releasing a new version of this site.
*
* A change applies to sessions started after it arrives, and a session already under way is never
* re-decided in place. Three changes do not wait for that session to end on its own, because
* their effect on it can be told without drawing again: a session sample rate of 0 while the
* visitor is being collected, a rate of 100 while they are not, and a stricter
* `defaultPrivacyLevel` while they are being collected — a visitor who is not being collected
* records nothing, so a stricter level has no plaintext to catch there. Each of those ends the
* current session, and the visitor's next action starts a new one under the new settings — the
* old session is collected to its end as it was begun, so no recording is left masked in one
* half and plain in the other. Every other change, a loosening privacy level included, waits for
* the next session.
* re-decided in place. Two changes do not wait for that session to end on its own, because their
* effect on it can be told without drawing again: a stricter `defaultPrivacyLevel`, and a session
* sample rate of 0. Both apply only while the visitor is being collected — one who is not records
* nothing and sends nothing, so neither has anything to act on there. Either ends the current
* session, and the visitor's next action starts a new one under the new settings; the old session
* is collected to its end as it was begun, so no recording is left masked in one half and plain
* in the other. Every other change waits for the next session, a loosening privacy level and a
* rate rising to 100 included — for "collect this visitor now" there is `setForcedSession()`.
*
* How soon "does not wait" is depends on when this client next hears of the change, and it hears
* only at page load and at each new session. A visitor who keeps loading pages hears within
* seconds; a single tab that is never reloaded hears nothing until its session reaches the
* four-hour cap.
*
* The values below stay in use until the first settings arrive, and whenever the settings cannot
* be reached.
Expand Down
56 changes: 19 additions & 37 deletions packages/rum-core/src/domain/rumSessionManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ describe('rum session manager', () => {
return getSessionState(SESSION_STORE_KEY).isExpired === '1'
}

describe('the three changes it can decide on its own', () => {
describe('the two changes it can decide on its own', () => {
it('ends a session being collected when the rate goes to zero', () => {
storeRemote({ version: 1, sessionSampleRate: 100, sessionReplaySampleRate: 100 })
startWith({ sessionSampleRate: 100 })
Expand All @@ -864,16 +864,6 @@ describe('rum session manager', () => {
expect(isSessionEnded()).toBeTrue()
})

it('ends a session that is not being collected when the rate goes to a hundred', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
startWith({ sessionSampleRate: 0 })
expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED)

deliver({ version: 2, sessionSampleRate: 100, sessionReplaySampleRate: 100 })

expect(isSessionEnded()).toBeTrue()
})

it('ends the session when the privacy level tightens', () => {
storeRemote({ version: 1, sessionSampleRate: 100, defaultPrivacyLevel: 'allow' })
startWith({ sessionSampleRate: 100, defaultPrivacyLevel: 'allow' })
Expand Down Expand Up @@ -934,14 +924,14 @@ describe('rum session manager', () => {
})

it('draws the session that follows on the settings that have just landed', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
startWith({ sessionSampleRate: 0 })
storeRemote({ version: 1, sessionSampleRate: 100, sessionReplaySampleRate: 100 })
startWith({ sessionSampleRate: 100 })

deliver({ version: 2, sessionSampleRate: 100, sessionReplaySampleRate: 100 })
deliver({ version: 2, sessionSampleRate: 0 })
clock.tick(STORAGE_POLL_DELAY)
document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK))

expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.TRACKED_WITH_SESSION_REPLAY)
expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED)
})
})

Expand All @@ -956,6 +946,20 @@ describe('rum session manager', () => {
expect(isSessionEnded()).toBeFalse()
})

it('leaves a session that is not being collected alone when the rate goes to a hundred', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
startWith({ sessionSampleRate: 0 })
expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED)

// The one rate whose outcome could be asserted and deliberately is not: `setForcedSession`
// already covers "collect this visitor now", raising volume unannounced is the one
// direction that surprises, and nothing about it is urgent.
deliver({ version: 2, sessionSampleRate: 100, sessionReplaySampleRate: 100 })

expect(expireSessionSpy).not.toHaveBeenCalled()
expect(isSessionEnded()).toBeFalse()
})

it('leaves a session that is not collected alone when the rate merely rises', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
startWith({ sessionSampleRate: 0 })
Expand Down Expand Up @@ -1166,28 +1170,6 @@ describe('rum session manager', () => {
return rumSessionManager
}

it('is still ended by a rate of a hundred when the session it adopted collects nothing', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
setCookie(SESSION_STORE_KEY, `rum=0&created=${Date.now()}&expire=${Date.now() + DURATION}`, DURATION)
const rumSessionManager = startWith({ sessionSampleRate: 0 })

// Forcing ends a session that collects nothing, so that the next draw can be the forced
// one. Before that draw happens, a tab that never forced anything starts a session of its
// own, and this page adopts it: the page is forced while the session it holds is not.
rumSessionManager.setForcedSession()
setCookie(SESSION_STORE_KEY, `rum=0&created=${Date.now()}&expire=${Date.now() + DURATION}`, DURATION)
clock.tick(STORAGE_POLL_DELAY)
document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK))
expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED)
expireSessionSpy.calls.reset()

// Here the rate has something to change, so the exemption does not apply: ending the
// session is what lets the next draw be the forced one this page asked for.
deliver({ version: 2, sessionSampleRate: 100 })

expect(isSessionEnded()).toBeTrue()
})

it('is not ended by a rate, since every draw it makes is collected anyway', () => {
storeRemote({ version: 1, sessionSampleRate: 0 })
startForced()
Expand Down
Loading
Loading