Skip to content

Commit d386426

Browse files
bug(telemetry): sessionId not unique
Problem: There seems to be a bug with vscode.env.sessionId since we observed in telemetry multiple clientIds who has the same sessionId. Solution: Generate our own sessionId and share it between extensions through globalThis Signed-off-by: nkomonen-amazon <[email protected]>
1 parent c716995 commit d386426

File tree

3 files changed

+55
-34
lines changed

3 files changed

+55
-34
lines changed

packages/core/src/shared/telemetry/activation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ export async function activate(
7979

8080
if (globals.telemetry.telemetryEnabled) {
8181
// Only log the IDs if telemetry is enabled, so that users who have it disabled do not think we are sending events.
82-
getLogger().info(`Telemetry Client ID: ${globals.telemetry.clientId}`)
83-
getLogger().info(`Telemetry Session ID: ${getSessionId()}`)
82+
getLogger().info(`Telemetry clientId: ${globals.telemetry.clientId}`)
83+
getLogger().info(`Telemetry sessionId: ${getSessionId()}`)
8484
}
8585
} catch (e) {
8686
// Only throw in a production build because:

packages/core/src/shared/telemetry/util.ts

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { randomUUID } from '../crypto'
2828
import { ClassToInterfaceType } from '../utilities/tsUtils'
2929
import { FunctionEntry, type TelemetryTracer } from './spans'
3030
import { telemetry } from './telemetry'
31-
import { v5 as uuidV5 } from 'uuid'
31+
import { v4 as uuidV4, v5 as uuidV5 } from 'uuid'
3232

3333
const legacySettingsTelemetryValueDisable = 'Disable'
3434
const legacySettingsTelemetryValueEnable = 'Enable'
@@ -98,33 +98,59 @@ export function convertLegacy(value: unknown): boolean {
9898
* can be used in conjunction with the client ID to differntiate between
9999
* different VS Code windows on a users machine.
100100
*
101-
* ### Rules:
102-
*
103-
* - On startup of a new application instance, a new session ID must be created.
104-
* - This identifier must be a `UUID`, it is enforced by the telemetry service.
105-
* - The session ID must be different from all other IDs on the machine
106-
* - A session ID exists until the application instance is terminated.
107-
* It should never be used again after termination.
108-
* - All extensions on the same application instance MUST return the same session ID.
109-
* - This will allow us to know which of our extensions (eg Q vs Toolkit) are in the
110-
* same VS Code window.
111-
*
112-
* `vscode.env.sessionId` behaves as described aboved, EXCEPT its
113-
* value looks close to a UUID by does not exactly match it (has additional characters).
114-
* As a result we process it through uuidV5 which creates a proper UUID from it.
115-
* uuidV5 is idempotent, so as long as `vscode.env.sessionId` returns the same value,
116-
* we will get the same UUID.
101+
* See spec: https://quip-amazon.com/9gqrAqwO5FCE
117102
*/
118-
export const getSessionId = once(() => uuidV5(vscode.env.sessionId, sessionIdNonce))
119-
/**
120-
* This is an arbitrary nonce that is used in creating a v5 UUID for Session ID. We only
121-
* have this since the spec requires it.
122-
* - This should ONLY be used by {@link getSessionId}.
123-
* - This value MUST NOT change during runtime, otherwise {@link getSessionId} will lose its
124-
* idempotency. But, if there was a reason to change the value in a PR, it would not be an issue.
125-
* - This is exported only for testing.
126-
*/
127-
export const sessionIdNonce = '44cfdb20-b30b-4585-a66c-9f48f24f99b5' as const
103+
export const getSessionId = once(() => SessionId.getSessionId())
104+
105+
class SessionId {
106+
public static getSessionId() {
107+
// This implementation does not work in web
108+
if (!isWeb()) {
109+
return this._getSessionId()
110+
}
111+
// A best effort at a sessionId just for web mode
112+
return this._getVscSessionId()
113+
}
114+
115+
/**
116+
* This implementation assumes that the `globalThis` is shared between extensions in the same
117+
* Extension Host, so we can share a global variable that way.
118+
*
119+
* This does not seem to work on web mode since the `globalThis` is not shared due to WebWorker design
120+
*/
121+
private static _getSessionId() {
122+
const global = globalThis as any
123+
if (global.amzn_sessionId === undefined) {
124+
;(globalThis as any).amzn_sessionId = uuidV4()
125+
}
126+
return global.amzn_sessionId
127+
}
128+
129+
/**
130+
* `vscode.env.sessionId` looks close to a UUID by does not exactly match it (has additional characters).
131+
* As a result we process it through uuidV5 which creates a proper UUID from it.
132+
* uuidV5 is idempotent, so as long as `vscode.env.sessionId` returns the same value,
133+
* we will get the same UUID.
134+
*
135+
* We were initially using this implementation for all session ids, but it has some caveats:
136+
* - If the extension host crashes, sesionId stays the same since the parent VSC process defines it and that does not crash.
137+
* We wanted it to generate a new sessionId on ext host crash.
138+
* - This value may not be reliable, see the following sessionId in telemetry, it contains many events
139+
* all from different client ids: `sessionId: cabea8e7-a8a1-5e51-a60e-07218f4a5937`
140+
*/
141+
private static _getVscSessionId() {
142+
return uuidV5(vscode.env.sessionId, this.sessionIdNonce)
143+
}
144+
/**
145+
* This is an arbitrary nonce that is used in creating a v5 UUID for Session ID. We only
146+
* have this since the spec requires it.
147+
* - This should ONLY be used by {@link getSessionId}.
148+
* - This value MUST NOT change during runtime, otherwise {@link getSessionId} will lose its
149+
* idempotency. But, if there was a reason to change the value in a PR, it would not be an issue.
150+
* - This is exported only for testing.
151+
*/
152+
private static readonly sessionIdNonce = '44cfdb20-b30b-4585-a66c-9f48f24f99b5'
153+
}
128154

129155
/**
130156
* Calculates the clientId for the current profile. This calculation is performed once

packages/core/src/test/shared/telemetry/util.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
getSessionId,
1313
getUserAgent,
1414
platformPair,
15-
sessionIdNonce,
1615
telemetryClientIdEnvKey,
1716
TelemetryConfig,
1817
} from '../../../shared/telemetry/util'
@@ -117,10 +116,6 @@ describe('getSessionId', function () {
117116
assert.deepStrictEqual(isUuid(result), true)
118117
assert.deepStrictEqual(getSessionId(), result, 'Subsequent call did not return the same UUID')
119118
})
120-
121-
it('nonce is the same as always', function () {
122-
assert.deepStrictEqual(sessionIdNonce, '44cfdb20-b30b-4585-a66c-9f48f24f99b5')
123-
})
124119
})
125120

126121
describe('getClientId', function () {

0 commit comments

Comments
 (0)