Skip to content

Commit 7fb034b

Browse files
authored
fix: do not extend session from discarded session replay spans (#939)
1 parent d05a8bc commit 7fb034b

File tree

7 files changed

+70
-43
lines changed

7 files changed

+70
-43
lines changed

packages/session-recorder/src/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,7 @@ const SplunkRumRecorder = {
194194
return
195195
}
196196

197-
if (SplunkRum._internalOnExternalSpanCreated) {
198-
SplunkRum._internalOnExternalSpanCreated()
199-
}
197+
let isExtended = false
200198

201199
// Safeguards from our ingest getting DDOSed:
202200
// 1. A session can send up to 4 hours of data
@@ -206,6 +204,13 @@ const SplunkRumRecorder = {
206204
return
207205
}
208206

207+
// We need to call it here before another getSessionID call. This will create a new session
208+
// if the previous one was expired
209+
if (SplunkRum._internalOnExternalSpanCreated) {
210+
SplunkRum._internalOnExternalSpanCreated()
211+
isExtended = true
212+
}
213+
209214
lastKnownSession = SplunkRum.getSessionId()
210215
sessionStartTime = Date.now()
211216
// reset counters
@@ -218,6 +223,12 @@ const SplunkRumRecorder = {
218223
return
219224
}
220225

226+
if (!isExtended) {
227+
if (SplunkRum._internalOnExternalSpanCreated) {
228+
SplunkRum._internalOnExternalSpanCreated()
229+
}
230+
}
231+
221232
const time = event.timestamp
222233
const eventI = eventCounter
223234
eventCounter += 1

packages/web/src/SessionBasedSampler.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@ export class SessionBasedSampler implements Sampler {
5252

5353
protected _upperBound: number
5454

55-
constructor({
56-
ratio = 1,
57-
sampled = new AlwaysOnSampler(),
58-
notSampled = new AlwaysOffSampler(),
59-
}: SessionBasedSamplerConfig = {}) {
55+
constructor(
56+
{
57+
ratio = 1,
58+
sampled = new AlwaysOnSampler(),
59+
notSampled = new AlwaysOffSampler(),
60+
}: SessionBasedSamplerConfig = {},
61+
private readonly useLocalStorageForSessionMetadata = false,
62+
) {
6063
this._ratio = this._normalize(ratio)
6164
this._upperBound = Math.floor(this._ratio * 0xffffffff)
6265

@@ -75,7 +78,7 @@ export class SessionBasedSampler implements Sampler {
7578
// Implementation based on @opentelemetry/core TraceIdRatioBasedSampler
7679
// but replacing deciding based on traceId with sessionId
7780
// (not extended from due to private methods)
78-
const currentSession = getRumSessionId()
81+
const currentSession = getRumSessionId({ useLocalStorage: this.useLocalStorageForSessionMetadata })
7982
if (this._currentSession !== currentSession) {
8083
this._currentSessionSampled = this._accumulate(currentSession) < this._upperBound
8184
this._currentSession = currentSession

packages/web/src/SplunkSpanAttributesProcessor.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import { getRumSessionId } from './session'
2323
export class SplunkSpanAttributesProcessor implements SpanProcessor {
2424
private readonly _globalAttributes: Attributes
2525

26-
constructor(globalAttributes: Attributes) {
26+
constructor(
27+
globalAttributes: Attributes,
28+
private useLocalStorageForSessionMetadata: boolean,
29+
) {
2730
this._globalAttributes = globalAttributes ?? {}
2831
}
2932

@@ -42,7 +45,10 @@ export class SplunkSpanAttributesProcessor implements SpanProcessor {
4245
onStart(span: Span): void {
4346
span.setAttribute('location.href', location.href)
4447
span.setAttributes(this._globalAttributes)
45-
span.setAttribute('splunk.rumSessionId', getRumSessionId())
48+
span.setAttribute(
49+
'splunk.rumSessionId',
50+
getRumSessionId({ useLocalStorage: this.useLocalStorageForSessionMetadata }),
51+
)
4652
span.setAttribute('browser.instance.visibility_state', document.visibilityState)
4753
}
4854

packages/web/src/index.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,16 @@ export const SplunkRum: SplunkOtelWebType = {
392392
return null
393393
}).filter((a): a is Exclude<typeof a, null> => Boolean(a))
394394

395-
this.attributesProcessor = new SplunkSpanAttributesProcessor({
396-
...(deploymentEnvironment
397-
? { 'environment': deploymentEnvironment, 'deployment.environment': deploymentEnvironment }
398-
: {}),
399-
...(version ? { 'app.version': version } : {}),
400-
...(processedOptions.globalAttributes || {}),
401-
})
395+
this.attributesProcessor = new SplunkSpanAttributesProcessor(
396+
{
397+
...(deploymentEnvironment
398+
? { 'environment': deploymentEnvironment, 'deployment.environment': deploymentEnvironment }
399+
: {}),
400+
...(version ? { 'app.version': version } : {}),
401+
...(processedOptions.globalAttributes || {}),
402+
},
403+
this._processedOptions.persistence === 'localStorage',
404+
)
402405
provider.addSpanProcessor(this.attributesProcessor)
403406

404407
if (processedOptions.beaconEndpoint) {
@@ -517,7 +520,11 @@ export const SplunkRum: SplunkOtelWebType = {
517520
},
518521

519522
getSessionId() {
520-
return getRumSessionId()
523+
if (!inited) {
524+
return undefined
525+
}
526+
527+
return getRumSessionId({ useLocalStorage: this._processedOptions.persistence === 'localStorage' })
521528
},
522529
_experimental_getSessionId() {
523530
return this.getSessionId()

packages/web/src/session/session.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import { context } from '@opentelemetry/api'
4545
with setting cookies, checking for inactivity, etc.
4646
*/
4747

48-
let rumSessionId: SessionId | undefined
4948
let recentActivity = false
5049
let cookieDomain: string
5150
let eventTarget: InternalEventTarget | undefined
@@ -93,8 +92,7 @@ export function updateSessionStatus({
9392
}
9493
}
9594

96-
rumSessionId = sessionState.id
97-
eventTarget?.emit('session-changed', { sessionId: rumSessionId })
95+
eventTarget?.emit('session-changed', { sessionId: sessionState.id })
9896

9997
if (recentActivity || forceActivity) {
10098
sessionState.expiresAt = Date.now() + SESSION_INACTIVITY_TIMEOUT_MS
@@ -157,17 +155,14 @@ export function initSessionTracking(
157155
if (hasNativeSessionId()) {
158156
// short-circuit and bail out - don't create cookie, watch for inactivity, or anything
159157
return {
160-
deinit: () => {
161-
rumSessionId = undefined
162-
},
158+
deinit: () => {},
163159
}
164160
}
165161

166162
if (domain) {
167163
cookieDomain = domain
168164
}
169165

170-
rumSessionId = instanceId
171166
recentActivity = true // document loaded implies activity
172167
eventTarget = newEventTarget
173168

@@ -179,16 +174,15 @@ export function initSessionTracking(
179174
return {
180175
deinit: () => {
181176
ACTIVITY_EVENTS.forEach((type) => document.removeEventListener(type, markActivity))
182-
rumSessionId = undefined
183177
eventTarget = undefined
184178
},
185179
}
186180
}
187181

188-
export function getRumSessionId(): SessionId | undefined {
182+
export function getRumSessionId({ useLocalStorage }: { useLocalStorage: boolean }): SessionId | undefined {
189183
if (hasNativeSessionId()) {
190184
return window['SplunkRumNative'].getNativeSessionId()
191185
}
192186

193-
return rumSessionId
187+
return getCurrentSessionState({ useLocalStorage, forceStoreRead: true })?.id
194188
}

packages/web/test/SplunkSpanAttributesProcessor.test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,26 @@ import { SplunkSpanAttributesProcessor } from '../src/SplunkSpanAttributesProces
2222
describe('SplunkSpanAttributesProcessor', () => {
2323
describe('setting global attribute', () => {
2424
it('should set attributes via constructor', () => {
25-
const processor = new SplunkSpanAttributesProcessor({
26-
key1: 'value1',
27-
})
25+
const processor = new SplunkSpanAttributesProcessor(
26+
{
27+
key1: 'value1',
28+
},
29+
false,
30+
)
2831

2932
expect(processor.getGlobalAttributes()).to.deep.eq({
3033
key1: 'value1',
3134
})
3235
})
3336

3437
it('should patch attributes via .setGlobalAttributes()', () => {
35-
const processor = new SplunkSpanAttributesProcessor({
36-
key1: 'value1',
37-
key2: 'value2',
38-
})
38+
const processor = new SplunkSpanAttributesProcessor(
39+
{
40+
key1: 'value1',
41+
key2: 'value2',
42+
},
43+
false,
44+
)
3945

4046
processor.setGlobalAttributes({
4147
key2: 'value2-updaged',

packages/web/test/session.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ describe('Session tracking', () => {
3838
// the init tests have possibly already started the setInterval for updateSessionStatus. Try to accomodate this.
3939
const provider = new SplunkWebTracerProvider()
4040
const trackingHandle = initSessionTracking(provider, '1234', new InternalEventTarget())
41-
const firstSessionId = getRumSessionId()
41+
const firstSessionId = getRumSessionId({ useLocalStorage: false })
4242
assert.strictEqual(firstSessionId.length, 32)
4343
// no marked activity, should keep same state
4444
updateSessionStatus({ forceStore: false, useLocalStorage: false })
45-
assert.strictEqual(firstSessionId, getRumSessionId())
45+
assert.strictEqual(firstSessionId, getRumSessionId({ useLocalStorage: false }))
4646
// set cookie to expire in 2 seconds, mark activity, and then updateSessionStatus.
4747
// Wait 4 seconds and cookie should still be there (having been renewed)
4848
const cookieValue = encodeURIComponent(
@@ -58,7 +58,7 @@ describe('Session tracking', () => {
5858
setTimeout(() => {
5959
// because of activity, same session should be there
6060
assert.ok(document.cookie.includes(SESSION_STORAGE_KEY))
61-
assert.strictEqual(firstSessionId, getRumSessionId())
61+
assert.strictEqual(firstSessionId, getRumSessionId({ useLocalStorage: false }))
6262

6363
// Finally, set a fake cookie with startTime 5 hours ago, update status, and find a new cookie with a new session ID
6464
// after max age code does its thing
@@ -74,7 +74,7 @@ describe('Session tracking', () => {
7474

7575
updateSessionStatus({ forceStore: true, useLocalStorage: false })
7676
assert.ok(document.cookie.includes(SESSION_STORAGE_KEY))
77-
const newSessionId = getRumSessionId()
77+
const newSessionId = getRumSessionId({ useLocalStorage: false })
7878
assert.strictEqual(newSessionId.length, 32)
7979
assert.ok(firstSessionId !== newSessionId)
8080

@@ -90,7 +90,7 @@ describe('Session tracking', () => {
9090

9191
function subject(allSpansAreActivity = false) {
9292
const provider = new SplunkWebTracerProvider()
93-
const firstSessionId = getRumSessionId()
93+
const firstSessionId = getRumSessionId({ useLocalStorage: false })
9494
initSessionTracking(provider, firstSessionId, new InternalEventTarget(), undefined, allSpansAreActivity)
9595

9696
provider.getTracer('tracer').startSpan('any-span').end()
@@ -138,9 +138,9 @@ describe('Session tracking - localStorage', () => {
138138
useLocalStorage,
139139
)
140140

141-
const firstSessionId = getRumSessionId()
141+
const firstSessionId = getRumSessionId({ useLocalStorage })
142142
updateSessionStatus({ forceStore: true, useLocalStorage })
143-
assert.strictEqual(firstSessionId, getRumSessionId())
143+
assert.strictEqual(firstSessionId, getRumSessionId({ useLocalStorage }))
144144

145145
trackingHandle.deinit()
146146
})

0 commit comments

Comments
 (0)