Skip to content

Commit 45bf5fd

Browse files
🔊 add telemetry for unexpected session id changes (#3815)
* 🔊 add telemetry for unexpected session id changes * 👌 add cookie values in the telemetry log Also, skip session state when there is no id (i.e. the cookie is expired) because it's noisy and isn't a problematic state.
1 parent 9ebdd36 commit 45bf5fd

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

packages/core/src/domain/session/sessionManager.ts

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Observable } from '../../tools/observable'
22
import type { Context } from '../../tools/serialisation/context'
33
import { createValueHistory } from '../../tools/valueHistory'
44
import type { RelativeTime } from '../../tools/utils/timeUtils'
5-
import { clocksOrigin, ONE_MINUTE, relativeNow } from '../../tools/utils/timeUtils'
5+
import { clocksOrigin, dateNow, ONE_MINUTE, relativeNow } from '../../tools/utils/timeUtils'
66
import { addEventListener, addEventListeners, DOM_EVENT } from '../../browser/addEventListener'
77
import { clearInterval, setInterval } from '../../tools/timer'
88
import type { Configuration } from '../configuration'
@@ -11,10 +11,15 @@ import { addTelemetryDebug } from '../telemetry'
1111
import { isSyntheticsTest } from '../synthetics/syntheticsWorkerValues'
1212
import type { CookieStore } from '../../browser/browser.types'
1313
import { getCurrentSite } from '../../browser/cookie'
14+
import { ExperimentalFeature, isExperimentalFeatureEnabled } from '../../tools/experimentalFeatures'
15+
import { findLast } from '../../tools/utils/polyfills'
16+
import { monitorError } from '../../tools/monitor'
1417
import { SESSION_NOT_TRACKED, SESSION_TIME_OUT_DELAY } from './sessionConstants'
1518
import { startSessionStore } from './sessionStore'
1619
import type { SessionState } from './sessionState'
20+
import { toSessionState } from './sessionState'
1721
import { retrieveSessionCookie } from './storeStrategies/sessionInCookie'
22+
import { SESSION_STORE_KEY } from './storeStrategies/sessionStoreStrategy'
1823

1924
export interface SessionManager<TrackingType extends string> {
2025
findSession: (
@@ -75,6 +80,12 @@ export function startSessionManager<TrackingType extends string>(
7580
// manager is started.
7681
sessionStore.expandOrRenewSession()
7782
sessionContextHistory.add(buildSessionContext(), clocksOrigin().relative)
83+
if (isExperimentalFeatureEnabled(ExperimentalFeature.SHORT_SESSION_INVESTIGATION)) {
84+
const session = sessionStore.getSession()
85+
if (session) {
86+
detectSessionIdChange(configuration, session)
87+
}
88+
}
7889

7990
trackingConsentState.observable.subscribe(() => {
8091
if (trackingConsentState.isGranted()) {
@@ -163,24 +174,69 @@ function trackResume(configuration: Configuration, cb: () => void) {
163174

164175
async function reportUnexpectedSessionState() {
165176
const rawSession = retrieveSessionCookie()
166-
let sessionCookies: string[] | Awaited<ReturnType<CookieStore['getAll']>> = []
167-
168-
if ('cookieStore' in window) {
169-
sessionCookies = await (window as { cookieStore: CookieStore }).cookieStore.getAll('_dd_s')
170-
} else {
171-
sessionCookies = document.cookie.split(/\s*;\s*/).filter((cookie) => cookie.startsWith('_dd_s'))
172-
}
173-
174177
addTelemetryDebug('Unexpected session state', {
175178
session: rawSession,
176179
isSyntheticsTest: isSyntheticsTest(),
177180
createdTimestamp: rawSession?.created,
178181
expireTimestamp: rawSession?.expire,
179-
cookie: {
180-
count: sessionCookies.length,
181-
domain: getCurrentSite(),
182-
...sessionCookies,
183-
},
182+
cookie: await getSessionCookies(),
184183
currentDomain: `${window.location.protocol}//${window.location.hostname}`,
185184
})
186185
}
186+
187+
function detectSessionIdChange(configuration: Configuration, initialSessionState: SessionState) {
188+
if (!window.cookieStore || !initialSessionState.created) {
189+
return
190+
}
191+
192+
const sessionCreatedTime = Number(initialSessionState.created)
193+
const sdkInitTime = dateNow()
194+
195+
const { stop } = addEventListener(configuration, cookieStore as CookieStore, DOM_EVENT.CHANGE, listener)
196+
stopCallbacks.push(stop)
197+
198+
function listener(event: CookieChangeEvent) {
199+
const changed = findLast(event.changed, (change): change is CookieListItem => change.name === SESSION_STORE_KEY)
200+
if (!changed) {
201+
return
202+
}
203+
204+
const sessionAge = dateNow() - sessionCreatedTime
205+
if (sessionAge > 14 * ONE_MINUTE) {
206+
// The session might have expired just because it's too old or lack activity
207+
stop()
208+
} else {
209+
const newSessionState = toSessionState(changed.value)
210+
if (newSessionState.id && newSessionState.id !== initialSessionState.id) {
211+
stop()
212+
const time = dateNow() - sdkInitTime
213+
getSessionCookies()
214+
.then((cookie) => {
215+
addTelemetryDebug('Session cookie changed', {
216+
time,
217+
session_age: sessionAge,
218+
old: initialSessionState,
219+
new: newSessionState,
220+
cookie,
221+
})
222+
})
223+
.catch(monitorError)
224+
}
225+
}
226+
}
227+
}
228+
229+
async function getSessionCookies(): Promise<{ count: number; domain: string }> {
230+
let sessionCookies: string[] | Awaited<ReturnType<CookieStore['getAll']>>
231+
if ('cookieStore' in window) {
232+
sessionCookies = await (window as { cookieStore: CookieStore }).cookieStore.getAll(SESSION_STORE_KEY)
233+
} else {
234+
sessionCookies = document.cookie.split(/\s*;\s*/).filter((cookie) => cookie.startsWith(SESSION_STORE_KEY))
235+
}
236+
237+
return {
238+
count: sessionCookies.length,
239+
domain: getCurrentSite(),
240+
...sessionCookies,
241+
}
242+
}

packages/core/src/tools/experimentalFeatures.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export enum ExperimentalFeature {
1919
EARLY_REQUEST_COLLECTION = 'early_request_collection',
2020
WATCH_COOKIE_WITHOUT_LOCK = 'watch_cookie_without_lock',
2121
USE_TREE_WALKER_FOR_ACTION_NAME = 'use_tree_walker_for_action_name',
22+
SHORT_SESSION_INVESTIGATION = 'short_session_investigation',
2223
}
2324

2425
const enabledExperimentalFeatures: Set<ExperimentalFeature> = new Set()

packages/core/src/tools/utils/polyfills.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export function findLast<T, S extends T>(
2-
array: T[],
3-
predicate: (item: T, index: number, array: T[]) => item is S
2+
array: readonly T[],
3+
predicate: (item: T, index: number, array: readonly T[]) => item is S
44
): S | undefined {
55
for (let i = array.length - 1; i >= 0; i -= 1) {
66
const item = array[i]

0 commit comments

Comments
 (0)