Skip to content

Commit 200c993

Browse files
committed
report telemetry to corresponding host
1 parent 70e1afe commit 200c993

File tree

18 files changed

+224
-90
lines changed

18 files changed

+224
-90
lines changed

.github/workflows/nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Update package.json
2323
run: |
2424
set -e
25-
setSegmentKey="setpath([\"segmentKey\"]; \"${{ secrets.ANALITYCS_KEY }}\")"
25+
setSegmentKey="setpath([\"segmentKey\"]; \"untrusted-dummy-key\")"
2626
setConfigcatKey="setpath([\"configcatKey\"]; \"${{ secrets.CONFIGCAT_KEY }}\")"
2727
jqCommands="${setSegmentKey} | ${setConfigcatKey}"
2828
cat package.json | jq "${jqCommands}" > package.json.tmp

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Update Segment
2020
run: |
2121
set -e
22-
setSegmentKey="setpath([\"segmentKey\"]; \"${{ secrets.ANALITYCS_KEY }}\")"
22+
setSegmentKey="setpath([\"segmentKey\"]; \"untrusted-dummy-key\")"
2323
setConfigcatKey="setpath([\"configcatKey\"]; \"${{ secrets.CONFIGCAT_KEY }}\")"
2424
jqCommands="${setSegmentKey} | ${setConfigcatKey}"
2525
cat package.json | jq "${jqCommands}" > package.json.tmp

src/authentication/authentication.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default class GitpodAuthenticationProvider extends Disposable implements
3434

3535
private _sessionsPromise: Promise<vscode.AuthenticationSession[]>;
3636

37-
private readonly flow: Readonly<UserFlowTelemetry> = { flow: 'auth' };
37+
private readonly flow = { flow: 'auth' } as Readonly<UserFlowTelemetry>;
3838

3939
constructor(
4040
private readonly context: vscode.ExtensionContext,

src/commands/logs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ILogService } from '../services/logService';
1313
import { INotificationService } from '../services/notificationService';
1414
import { ITelemetryService, UserFlowTelemetry } from '../services/telemetryService';
1515
import { Configuration } from '../configuration';
16+
import { HostService } from '../services/hostService';
1617

1718
interface IFile {
1819
path: string;
@@ -27,10 +28,12 @@ export class ExportLogsCommand implements Command {
2728
private readonly notificationService: INotificationService,
2829
private readonly telemetryService: ITelemetryService,
2930
private readonly logService: ILogService,
31+
private readonly hostService: HostService,
3032
) { }
3133

3234
async execute() {
33-
const flow: UserFlowTelemetry = { flow: 'export_logs' };
35+
const gitpodHost = this.hostService.gitpodHost;
36+
const flow: UserFlowTelemetry = { gitpodHost, flow: 'export_logs' };
3437
this.telemetryService.sendUserFlowStatus('exporting', flow);
3538
try {
3639
await this.exportLogs();

src/common/telemetry.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,10 @@ export class BaseTelemetryAppender implements ITelemetryAppender {
6363
private _exceptionQueue: Array<{ exception: Error; data: AppenderData | undefined }> = [];
6464

6565
// Necessary information to create a telemetry client
66-
private _clientFactory: (key: string) => Promise<BaseTelemetryClient>;
67-
private _key: string;
66+
private _clientFactory: () => Promise<BaseTelemetryClient>;
6867

69-
constructor(key: string, clientFactory: (key: string) => Promise<BaseTelemetryClient>) {
68+
constructor(clientFactory: () => Promise<BaseTelemetryClient>) {
7069
this._clientFactory = clientFactory;
71-
this._key = key;
7270
if (getTelemetryLevel() !== TelemetryLevel.OFF) {
7371
this.instantiateAppender();
7472
}
@@ -124,20 +122,25 @@ export class BaseTelemetryAppender implements ITelemetryAppender {
124122
this._exceptionQueue = [];
125123
}
126124

125+
private isInstantiating = false;
126+
127127
/**
128128
* Instantiates the telemetry client to make the appender "active"
129129
*/
130130
instantiateAppender(): void {
131-
if (this._isInstantiated) {
131+
if (this.isInstantiating || this._isInstantiated) {
132132
return;
133133
}
134+
this.isInstantiating = true;
134135
// Call the client factory to get the client and then let it know it's instatntiated
135-
this._clientFactory(this._key).then(client => {
136+
this._clientFactory().then(client => {
136137
this._telemetryClient = client;
137138
this._isInstantiated = true;
138139
this._flushQueues();
139140
}).catch(err => {
140141
console.error(err);
142+
}).finally(() => {
143+
this.isInstantiating = false;
141144
});
142145
}
143146
}
@@ -146,12 +149,13 @@ export class BaseTelemetryReporter extends Disposable {
146149
private userOptIn = false;
147150
private errorOptIn = false;
148151
private _extension: vscode.Extension<any> | undefined;
152+
private readonly appenders = new Map<string, ITelemetryAppender>();
149153

150154
constructor(
151155
private extensionId: string,
152156
private extensionVersion: string,
153-
private telemetryAppender: ITelemetryAppender,
154157
private osShim: { release: string; platform: string; architecture: string },
158+
private readonly clientFactory: (gitpodHost: string) => Promise<BaseTelemetryClient>
155159
) {
156160
super();
157161

@@ -173,7 +177,7 @@ export class BaseTelemetryReporter extends Disposable {
173177
this.userOptIn = telemetryLevel === TelemetryLevel.ON;
174178
this.errorOptIn = telemetryLevel === TelemetryLevel.ERROR || this.userOptIn;
175179
if (this.userOptIn || this.errorOptIn) {
176-
this.telemetryAppender.instantiateAppender();
180+
this.appenders.forEach(appender => appender.instantiateAppender());
177181
}
178182
}
179183

@@ -354,11 +358,11 @@ export class BaseTelemetryReporter extends Disposable {
354358
* @param eventName The name of the event
355359
* @param properties The properties to send with the event
356360
*/
357-
public sendTelemetryEvent(eventName: string, properties?: TelemetryEventProperties): void {
361+
public sendTelemetryEvent(gitpodHost: string, eventName: string, properties?: TelemetryEventProperties): void {
358362
if (this.userOptIn && eventName !== '') {
359363
properties = { ...properties, ...this.getCommonProperties() };
360364
const cleanProperties = this.cloneAndChange(properties, (_key: string, prop: string) => this.anonymizeFilePaths(prop, false));
361-
this.telemetryAppender.logEvent(`${eventName}`, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
365+
this.getAppender(gitpodHost).logEvent(`${eventName}`, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
362366
}
363367
}
364368

@@ -367,10 +371,10 @@ export class BaseTelemetryReporter extends Disposable {
367371
* @param eventName The name of the event
368372
* @param properties The properties to send with the event
369373
*/
370-
public sendRawTelemetryEvent(eventName: string, properties?: RawTelemetryEventProperties): void {
374+
public sendRawTelemetryEvent(gitpodHost: string, eventName: string, properties?: RawTelemetryEventProperties): void {
371375
if (this.userOptIn && eventName !== '') {
372376
properties = { ...properties, ...this.getCommonProperties() };
373-
this.telemetryAppender.logEvent(`${eventName}`, { properties });
377+
this.getAppender(gitpodHost).logEvent(`${eventName}`, { properties });
374378
}
375379
}
376380

@@ -379,14 +383,14 @@ export class BaseTelemetryReporter extends Disposable {
379383
* @param eventName The name of the event
380384
* @param properties The properties to send with the event
381385
*/
382-
public sendTelemetryErrorEvent(eventName: string, properties?: { [key: string]: string }): void {
386+
public sendTelemetryErrorEvent(gitpodHost: string, eventName: string, properties?: { [key: string]: string }): void {
383387
if (this.errorOptIn && eventName !== '') {
384388
// always clean the properties if first party
385389
// do not send any error properties if we shouldn't send error telemetry
386390
// if we have no errorProps, assume all are error props
387391
properties = { ...properties, ...this.getCommonProperties() };
388392
const cleanProperties = this.cloneAndChange(properties, (_key: string, prop: string) => this.anonymizeFilePaths(prop, false));
389-
this.telemetryAppender.logEvent(`${eventName}`, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
393+
this.getAppender(gitpodHost).logEvent(`${eventName}`, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
390394
}
391395
}
392396

@@ -395,20 +399,31 @@ export class BaseTelemetryReporter extends Disposable {
395399
* @param error The error to send
396400
* @param properties The properties to send with the event
397401
*/
398-
public sendTelemetryException(error: Error, properties?: TelemetryEventProperties): void {
402+
public sendTelemetryException(gitpodHost: string, error: Error, properties?: TelemetryEventProperties): void {
399403
if (this.errorOptIn && error) {
400404
properties = { ...properties, ...this.getCommonProperties() };
401405
const cleanProperties = this.cloneAndChange(properties, (_key: string, prop: string) => this.anonymizeFilePaths(prop, false));
402406
// Also run the error stack through the anonymizer
403407
if (error.stack) {
404408
error.stack = this.anonymizeFilePaths(error.stack, false);
405409
}
406-
this.telemetryAppender.logException(error, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
410+
this.getAppender(gitpodHost).logException(error, { properties: this.removePropertiesWithPossibleUserInfo(cleanProperties) });
411+
}
412+
}
413+
414+
private getAppender(gitpodHost: string) {
415+
const gitpodHostUrl = new URL(gitpodHost);
416+
const serviceUrl = gitpodHostUrl.toString().replace(/\/$/, '').toLowerCase();
417+
let appender = this.appenders.get(serviceUrl);
418+
if (!appender) {
419+
appender = new BaseTelemetryAppender(() => this.clientFactory(serviceUrl));
420+
this.appenders.set(serviceUrl, appender);
407421
}
422+
return appender;
408423
}
409424

410425
public override async dispose(): Promise<void> {
411426
super.dispose();
412-
await this.telemetryAppender.flush();
427+
await Promise.allSettled([...this.appenders.values()].map(a => a.flush()));
413428
}
414429
}

src/daemonStarter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export async function ensureDaemonStarted(logService: ILogService, telemetryServ
2828
}
2929
logService.error('lssh unexpectedly exit with code: ' + code + ' attempt retry: ' + retry);
3030
resolve(false);
31-
telemetryService.sendTelemetryException(new Error(`unexpectedly exit with code ${humanReadableCode} ${code} attempt retry: ${retry}`), { humanReadableCode, code: code?.toString() ?? 'null' });
31+
telemetryService.sendTelemetryException(
32+
Configuration.getGitpodHost(),
33+
new Error(`unexpectedly exit with code ${humanReadableCode} ${code} attempt retry: ${retry}`),
34+
{ humanReadableCode, code: code?.toString() ?? 'null' }
35+
);
3236
});
3337
});
3438
if (!ok) {

src/extension.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { SignInCommand } from './commands/account';
2323
import { ExportLogsCommand } from './commands/logs';
2424
import { ensureDaemonStarted } from './daemonStarter';
2525
import { ExtensionServiceServer } from './local-ssh/ipc/extension';
26+
import { Configuration } from './configuration';
2627

2728
// connect-web uses fetch api, so we need to polyfill it
2829
if (!global.fetch) {
@@ -39,6 +40,7 @@ const FIRST_INSTALL_KEY = 'gitpod-desktop.firstInstall';
3940
let telemetryService: TelemetryService | undefined;
4041
let remoteSession: RemoteSession | undefined;
4142
let logger: vscode.LogOutputChannel | undefined;
43+
let hostService: HostService | undefined;
4244

4345
export async function activate(context: vscode.ExtensionContext) {
4446
const extensionId = context.extension.id;
@@ -61,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext) {
6163

6264
logger.info(`${extensionId}/${packageJSON.version} (${os.release()} ${os.platform()} ${os.arch()}) vscode/${vscode.version} (${vscode.env.appName})`);
6365

64-
telemetryService = new TelemetryService(extensionId, packageJSON.version, packageJSON.segmentKey, logger!, context.extensionMode === vscode.ExtensionMode.Production);
66+
telemetryService = new TelemetryService(extensionId, packageJSON.version, packageJSON.segmentKey, logger!);
6567

6668
const notificationService = new NotificationService(telemetryService);
6769

@@ -72,7 +74,7 @@ export async function activate(context: vscode.ExtensionContext) {
7274
const authProvider = new GitpodAuthenticationProvider(context, logger, telemetryService, notificationService);
7375
context.subscriptions.push(authProvider);
7476

75-
const hostService = new HostService(context, notificationService, logger);
77+
hostService = new HostService(context, notificationService, logger);
7678
context.subscriptions.push(hostService);
7779

7880
const sessionService = new SessionService(hostService, logger);
@@ -105,13 +107,13 @@ export async function activate(context: vscode.ExtensionContext) {
105107

106108
// Register global commands
107109
commandManager.register(new SignInCommand(sessionService));
108-
commandManager.register(new ExportLogsCommand(context.logUri, notificationService, telemetryService, logger));
110+
commandManager.register(new ExportLogsCommand(context.logUri, notificationService, telemetryService, logger, hostService));
109111

110112
ensureDaemonStarted(logger, telemetryService, 3).then(() => { }).catch(e => { logger?.error(e) });
111113

112114
if (!context.globalState.get<boolean>(FIRST_INSTALL_KEY, false)) {
113115
context.globalState.update(FIRST_INSTALL_KEY, true);
114-
telemetryService.sendTelemetryEvent('gitpod_desktop_installation', { kind: 'install' });
116+
telemetryService.sendTelemetryEvent(hostService.gitpodHost, 'gitpod_desktop_installation', { kind: 'install' });
115117
}
116118

117119
remoteConnectionInfo = getGitpodRemoteWindowConnectionInfo(context);
@@ -120,17 +122,17 @@ export async function activate(context: vscode.ExtensionContext) {
120122
if (remoteConnectionInfo) {
121123
commandManager.register({ id: 'gitpod.api.autoTunnel', execute: () => remoteConnector.autoTunnelCommand });
122124

123-
remoteSession = new RemoteSession(remoteConnectionInfo.remoteAuthority, remoteConnectionInfo.connectionInfo, context, hostService, sessionService, settingsSync, experiments, logger!, telemetryService!, notificationService);
125+
remoteSession = new RemoteSession(remoteConnectionInfo.remoteAuthority, remoteConnectionInfo.connectionInfo, context, hostService!, sessionService, settingsSync, experiments, logger!, telemetryService!, notificationService);
124126
await remoteSession.initialize();
125127
} else if (sessionService.isSignedIn()) {
126-
const restartFlow = { flow: 'restart_workspace', userId: sessionService.getUserId() };
127-
checkForStoppedWorkspaces(context, hostService.gitpodHost, restartFlow, notificationService, logger!);
128+
const restartFlow = { flow: 'restart_workspace', userId: sessionService.getUserId(), gitpodHost: hostService!.gitpodHost };
129+
checkForStoppedWorkspaces(context, restartFlow, notificationService, logger!);
128130
}
129131
});
130132

131133
success = true;
132134
} catch (e) {
133-
telemetryService?.sendTelemetryException(e);
135+
telemetryService?.sendTelemetryException(hostService?.gitpodHost || Configuration.getGitpodHost(), e);
134136
throw e;
135137
} finally {
136138
const rawActivateProperties = {
@@ -142,8 +144,9 @@ export async function activate(context: vscode.ExtensionContext) {
142144
debugWorkspace: remoteConnectionInfo ? String(!!remoteConnectionInfo.connectionInfo.debugWorkspace) : '',
143145
success: String(success)
144146
};
147+
const gitpodHost = rawActivateProperties.gitpodHost || hostService?.gitpodHost || Configuration.getGitpodHost();
145148
logger?.info('Activation properties:', JSON.stringify(rawActivateProperties, undefined, 2));
146-
telemetryService?.sendTelemetryEvent('vscode_desktop_activate', {
149+
telemetryService?.sendTelemetryEvent(gitpodHost, 'vscode_desktop_activate', {
147150
...rawActivateProperties,
148151
remoteUri: String(!!rawActivateProperties.remoteUri)
149152
});

src/heartbeat.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class HeartbeatManager extends Disposable {
131131
? (!wasClosed ? await this.sessionService.getAPI().sendHeartbeat(this.connectionInfo.workspaceId) : await this.sessionService.getAPI().sendDidClose(this.connectionInfo.workspaceId))
132132
: await service.server.sendHeartBeat({ instanceId: this.connectionInfo.instanceId, wasClosed });
133133
if (wasClosed) {
134-
this.telemetryService.sendTelemetryEvent('ide_close_signal', { workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId, gitpodHost: this.connectionInfo.gitpodHost, clientKind: 'vscode', debugWorkspace: String(!!this.connectionInfo.debugWorkspace) });
134+
this.telemetryService.sendTelemetryEvent(this.connectionInfo.gitpodHost, 'ide_close_signal', { workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId, gitpodHost: this.connectionInfo.gitpodHost, clientKind: 'vscode', debugWorkspace: String(!!this.connectionInfo.debugWorkspace) });
135135
this.logService.trace(`Send closed heartbeat`);
136136
} else {
137137
this.logService.trace(`Send heartbeat, triggered by ${this.lastActivityEvent} event`);
@@ -147,7 +147,7 @@ export class HeartbeatManager extends Disposable {
147147
e.message = `Failed to send ${suffix}, triggered by event: ${this.lastActivityEvent}: ${originMsg}`;
148148
this.logService.error(e);
149149
e.message = `Failed to send ${suffix}: ${originMsg}`;
150-
this.telemetryService.sendTelemetryException(e, { workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId});
150+
this.telemetryService.sendTelemetryException(this.connectionInfo.gitpodHost, e, { workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId});
151151
}
152152
}
153153

@@ -159,7 +159,7 @@ export class HeartbeatManager extends Disposable {
159159
}
160160

161161
private sendEventData() {
162-
this.telemetryService.sendRawTelemetryEvent('vscode_desktop_heartbeat_delta', { events: Object.fromEntries(this.eventCounterMap), workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId, gitpodHost: this.connectionInfo.gitpodHost, clientKind: 'vscode' });
162+
this.telemetryService.sendRawTelemetryEvent(this.connectionInfo.gitpodHost, 'vscode_desktop_heartbeat_delta', { events: Object.fromEntries(this.eventCounterMap), workspaceId: this.connectionInfo.workspaceId, instanceId: this.connectionInfo.instanceId, gitpodHost: this.connectionInfo.gitpodHost, clientKind: 'vscode' });
163163
this.eventCounterMap.clear();
164164
}
165165

0 commit comments

Comments
 (0)