From df6bf60911b6a8bab06f2ca4274fbad06426d3fb Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Sep 2022 10:37:01 -0400 Subject: [PATCH 1/8] First run of scaffolding out a telemetry API --- .../workbench/api/common/extHost.api.impl.ts | 4 + .../workbench/api/common/extHostTelemetry.ts | 64 +++++++++++++-- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.telemetryLogger.d.ts | 81 +++++++++++++++++++ 4 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.telemetryLogger.d.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1d5ea634ddd17..c2bd75ca55ed9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -327,6 +327,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const installAge = Date.now() - new Date(initData.telemetryInfo.firstSessionDate).getTime(); return isNaN(installAge) ? false : installAge < 1000 * 60 * 60 * 24; // install age is less than a day }, + createTelemetryLogger(appender: vscode.TelemetryAppender): vscode.TelemetryLogger { + checkProposedApiEnabled(extension, 'telemetry'); + return extHostTelemetry.instantiateLogger(extension.identifier.value, appender); + }, openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string }) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority, diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index d27d0a9047d8a..b213e6c63a54b 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -3,28 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type * as vscode from 'vscode'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTelemetryShape, MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import type { TelemetryConfiguration } from 'vscode'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostTelemetry implements ExtHostTelemetryShape { private readonly _onDidChangeTelemetryEnabled = new Emitter(); readonly onDidChangeTelemetryEnabled: Event = this._onDidChangeTelemetryEnabled.event; - private readonly _onDidChangeTelemetryConfiguration = new Emitter(); - readonly onDidChangeTelemetryConfiguration: Event = this._onDidChangeTelemetryConfiguration.event; + private readonly _onDidChangeTelemetryConfiguration = new Emitter(); + readonly onDidChangeTelemetryConfiguration: Event = this._onDidChangeTelemetryConfiguration.event; private _productConfig: { usage: boolean; error: boolean } = { usage: true, error: true }; private _level: TelemetryLevel = TelemetryLevel.NONE; private _oldTelemetryEnablement: boolean | undefined; + private readonly _telemetryLoggers = new Map(); + private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; + + constructor(@IExtHostRpcService rpc: IExtHostRpcService) { + this._mainThreadTelemetryProxy = rpc.getProxy(MainContext.MainThreadTelemetry); + } getTelemetryConfiguration(): boolean { return this._level === TelemetryLevel.USAGE; } - getTelemetryDetails(): TelemetryConfiguration { + getTelemetryDetails(): vscode.TelemetryConfiguration { return { isCrashEnabled: this._level >= TelemetryLevel.CRASH, isErrorsEnabled: this._productConfig.error ? this._level >= TelemetryLevel.ERROR : false, @@ -32,6 +39,12 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { }; } + instantiateLogger(extensionId: string, appender: vscode.TelemetryAppender) { + const logger = new ExtHostTelemetryLogger(appender); + this._telemetryLoggers.set(extensionId, logger); + return logger.apiTelemetryLogger; + } + $initializeTelemetryLevel(level: TelemetryLevel, productConfig?: { usage: boolean; error: boolean }): void { this._level = level; this._productConfig = productConfig || { usage: true, error: true }; @@ -47,5 +60,46 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { } } +export class ExtHostTelemetryLogger { + private _appender: vscode.TelemetryAppender; + private readonly _onDidChangeEnableStates = new Emitter(); + private _apiObject: vscode.TelemetryLogger | undefined; + constructor(appender: vscode.TelemetryAppender) { + this._appender = appender; + } + + + logUsage(eventName: string, data?: Record): void { + this._appender.logEvent(eventName, data); + } + + logException(exception: Error, data?: Record): void { + this._appender.logException(exception, data); + } + + get apiTelemetryLogger(): vscode.TelemetryLogger { + if (!this._apiObject) { + const that = this; + const obj: vscode.TelemetryLogger = { + logUsage: that.logUsage, + isErrorsEnabled: true, + isUsageEnabled: true, + logError: that.logUsage, + dispose: that.dispose, + logException: that.logException, + onDidChangeEnableStates: that._onDidChangeEnableStates.event + }; + this._apiObject = Object.freeze(obj); + } + return this._apiObject; + } + + dispose(): void { + if (this._appender?.flush) { + this._appender.flush(); + } + } +} + export const IExtHostTelemetry = createDecorator('IExtHostTelemetry'); export interface IExtHostTelemetry extends ExtHostTelemetry, ExtHostTelemetryShape { } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 3106f775a7cf4..871da64229501 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -58,6 +58,7 @@ export const allApiProposals = Object.freeze({ taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', telemetryLog: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetryLog.d.ts', + telemetryLogger: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts', terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', diff --git a/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts b/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts new file mode 100644 index 0000000000000..a97ccfcf60bb7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface TelemetryLogger { + //TODO feels weird having this on all loggers + readonly onDidChangeEnableStates: Event; + readonly isUsageEnabled: boolean; + readonly isErrorsEnabled: boolean; + + /** + * After completing cleaning, telemetry setting checks, and data mix-in calls `TelemetryAppender.logEvent` to log the event. + * Automatically supports echoing to extension telemetry output channel. + * @param eventName The event name to log + * @param data The data to log + */ + logUsage(eventName: string, data?: Record): void; + + /** + * After completing cleaning, telemetry setting checks, and data mix-in calls `TelemetryAppender.logEvent` to log the event. Differs from `logUsage` in that it will log the event if the telemetry setting is Error+. + * Automatically supports echoing to extension telemetry output channel. + * @param eventName The event name to log + * @param data The data to log + */ + logError(eventName: string, data?: Record): void; + + /** + * Calls `TelemetryAppender.logException`. Does cleaning, telemetry checks, and data mix-in. + * Automatically supports echoing to extension telemetry output channel. + * Will also automatically log any exceptions thrown within the extension host process. + * @param exception The error object which contains the stack trace cleaned of PII + * @param data Additional data to log alongside the stack trace + */ + logException(exception: Error, data?: Record): void; + + dispose(): void; + } + + export interface TelemetryAppender { + /** + * Whether or not you want to avoid having the built-in common properties such as os, extension name, etc injected into the data object. + */ + readonly ignorBuiltInCommonProperties: boolean; + + /** + * Any additional common properties which should be injected into the data object. + */ + readonly additionalCommonProperties: Record; + + /** + * User-defined function which logs an event, used within the TelemetryLogger + * @param eventName The name of the event which you are logging + * @param data A serializable key value pair that is being logged + */ + logEvent(eventName: string, data?: Record): void; + + /** + * User-defined function which logs an error, used within the TelemetryLogger + * @param exception The exception being logged + * @param data Any additional data to be collected with the exception + */ + logException(exception: Error, data?: Record): void; + + /** + * Optional flush function which will give your appender one last chance to send any remaining events as the TelemetryLogger is being disposed + */ + flush?(): void | Thenable; + } + + export namespace env { + /** + * A wrapper around a TelemetryAppender which provides built-in setting checks, common properties, data cleaning, output channel logging, and internal ext host process exception catching. + * @param appender The core piece which we call when it is time to log telemetry. It is highly recommended that you don't call the methods within the appender directly as the logger provides extra guards and cleaning. + * @returns An instantiated telemetry logger which you can use for recording telemetry + */ + export function createTelemetryLogger(appender: TelemetryAppender): TelemetryLogger; + } +} From cdc011409d8d313afbeeb109a0ce7956cab83d5f Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Sep 2022 15:29:11 -0400 Subject: [PATCH 2/8] Make telemetry config up to date --- .../workbench/api/common/extHostTelemetry.ts | 32 ++++++++++++++----- .../vscode.proposed.telemetryLogger.d.ts | 4 +-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index b213e6c63a54b..5d2ed3ac708ff 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -40,7 +40,8 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { } instantiateLogger(extensionId: string, appender: vscode.TelemetryAppender) { - const logger = new ExtHostTelemetryLogger(appender); + const telemetryDetails = this.getTelemetryDetails(); + const logger = new ExtHostTelemetryLogger(appender, { isUsageEnabled: telemetryDetails.isUsageEnabled, isErrorsEnabled: telemetryDetails.isErrorsEnabled }); this._telemetryLoggers.set(extensionId, logger); return logger.apiTelemetryLogger; } @@ -63,18 +64,30 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { export class ExtHostTelemetryLogger { private _appender: vscode.TelemetryAppender; private readonly _onDidChangeEnableStates = new Emitter(); + private _telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }; private _apiObject: vscode.TelemetryLogger | undefined; - constructor(appender: vscode.TelemetryAppender) { + constructor(appender: vscode.TelemetryAppender, telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }) { this._appender = appender; + this._telemetryEnablements = { isUsageEnabled: telemetryEnablements.isUsageEnabled, isErrorsEnabled: telemetryEnablements.isErrorsEnabled }; } + updateTelemetryEnablements(isUsageEnabled: boolean, isErrorsEnabled: boolean): void { + if (this._apiObject) { + this._telemetryEnablements = { isUsageEnabled, isErrorsEnabled }; + this._onDidChangeEnableStates.fire(this._apiObject); + } + } logUsage(eventName: string, data?: Record): void { this._appender.logEvent(eventName, data); } - logException(exception: Error, data?: Record): void { - this._appender.logException(exception, data); + logError(eventNameOrException: Error | string, data?: Record): void { + if (typeof eventNameOrException === 'string') { + this._appender.logEvent(eventNameOrException, data); + } else { + this._appender.logException(eventNameOrException, data); + } } get apiTelemetryLogger(): vscode.TelemetryLogger { @@ -82,11 +95,14 @@ export class ExtHostTelemetryLogger { const that = this; const obj: vscode.TelemetryLogger = { logUsage: that.logUsage, - isErrorsEnabled: true, - isUsageEnabled: true, - logError: that.logUsage, + get isUsageEnabled() { + return that._telemetryEnablements.isUsageEnabled; + }, + get isErrorsEnabled() { + return that._telemetryEnablements.isErrorsEnabled; + }, + logError: that.logError, dispose: that.dispose, - logException: that.logException, onDidChangeEnableStates: that._onDidChangeEnableStates.event }; this._apiObject = Object.freeze(obj); diff --git a/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts b/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts index a97ccfcf60bb7..dfd38a77dd842 100644 --- a/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts +++ b/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts @@ -34,7 +34,7 @@ declare module 'vscode' { * @param exception The error object which contains the stack trace cleaned of PII * @param data Additional data to log alongside the stack trace */ - logException(exception: Error, data?: Record): void; + logError(exception: Error, data?: Record): void; dispose(): void; } @@ -43,7 +43,7 @@ declare module 'vscode' { /** * Whether or not you want to avoid having the built-in common properties such as os, extension name, etc injected into the data object. */ - readonly ignorBuiltInCommonProperties: boolean; + readonly ignoreBuiltInCommonProperties: boolean; /** * Any additional common properties which should be injected into the data object. From c943d047fe6a9adba153db7b8152e44f5c1c7b27 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 9 Sep 2022 12:48:58 -0400 Subject: [PATCH 3/8] Further work on telemetry API --- .../telemetry/common/telemetryUtils.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHostTelemetry.ts | 87 +++++++++++++++++-- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index fd8e1a4f9728a..cd0cb5009ecbb 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -191,7 +191,7 @@ export function validateTelemetryData(data?: any): { properties: Properties; mea }; } -const telemetryAllowedAuthorities: readonly string[] = ['ssh-remote', 'dev-container', 'attached-container', 'wsl', 'tunneling']; +const telemetryAllowedAuthorities: readonly string[] = ['ssh-remote', 'dev-container', 'attached-container', 'wsl', 'tunneling', 'codespaces']; export function cleanRemoteAuthority(remoteAuthority?: string): string { if (!remoteAuthority) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c2bd75ca55ed9..8c7a7f7d28b44 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -329,7 +329,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, createTelemetryLogger(appender: vscode.TelemetryAppender): vscode.TelemetryLogger { checkProposedApiEnabled(extension, 'telemetry'); - return extHostTelemetry.instantiateLogger(extension.identifier.value, appender); + return extHostTelemetry.instantiateLogger(extension, appender); }, openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string }) { return extHostWindow.openUri(uri, { diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index 5d2ed3ac708ff..8f734a3239c98 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -9,6 +9,14 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostTelemetryShape, MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILoggerService } from 'vs/platform/log/common/log'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; +import { mixin } from 'vs/base/common/objects'; export class ExtHostTelemetry implements ExtHostTelemetryShape { private readonly _onDidChangeTelemetryEnabled = new Emitter(); @@ -23,7 +31,13 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { private readonly _telemetryLoggers = new Map(); private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; - constructor(@IExtHostRpcService rpc: IExtHostRpcService) { + constructor( + @IExtHostRpcService rpc: IExtHostRpcService, + @IExtHostFileSystemInfo extHostFileSystemInfo: IExtHostFileSystemInfo, + @IExtHostInitDataService private readonly initData: IExtHostInitDataService, + @ILoggerService loggerService: ILoggerService, + ) { + //const log = loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); this._mainThreadTelemetryProxy = rpc.getProxy(MainContext.MainThreadTelemetry); } @@ -39,10 +53,10 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { }; } - instantiateLogger(extensionId: string, appender: vscode.TelemetryAppender) { + instantiateLogger(extension: IExtensionDescription, appender: vscode.TelemetryAppender) { const telemetryDetails = this.getTelemetryDetails(); - const logger = new ExtHostTelemetryLogger(appender, { isUsageEnabled: telemetryDetails.isUsageEnabled, isErrorsEnabled: telemetryDetails.isErrorsEnabled }); - this._telemetryLoggers.set(extensionId, logger); + const logger = new ExtHostTelemetryLogger(appender, this.getBuiltInCommonProperties(extension), { isUsageEnabled: telemetryDetails.isUsageEnabled, isErrorsEnabled: telemetryDetails.isErrorsEnabled }); + this._telemetryLoggers.set(extension.identifier.value, logger); return logger.apiTelemetryLogger; } @@ -51,9 +65,44 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { this._productConfig = productConfig || { usage: true, error: true }; } + getBuiltInCommonProperties(extension: IExtensionDescription): Record { + const commonProperties: Record = {}; + // TODO @lramos15, does os info like node arch, platform version, etc exist here. + // Or will first party extensions just mix this in + commonProperties['common.extname'] = extension.name; + commonProperties['common.extversion'] = extension.version; + commonProperties['common.vscodemachineid'] = this.initData.telemetryInfo.machineId; + commonProperties['common.vscodesessionid'] = this.initData.telemetryInfo.sessionId; + commonProperties['common.vscodeversion'] = this.initData.version; + //commonProperties['common.isnewappinstall'] = this.initData.isNewAppInstall ? this.vscodeAPI.env.isNewAppInstall.toString() : "false"; + commonProperties['common.product'] = this.initData.environment.appHost; + + switch (this.initData.uiKind) { + case UIKind.Web: + commonProperties['common.uikind'] = 'web'; + break; + case UIKind.Desktop: + commonProperties['common.uikind'] = 'desktop'; + break; + default: + commonProperties['common.uikind'] = 'unknown'; + } + + commonProperties['common.remotename'] = getRemoteName(cleanRemoteAuthority(this.initData.remote.authority)); + + return commonProperties; + } + $onDidChangeTelemetryLevel(level: TelemetryLevel): void { this._oldTelemetryEnablement = this.getTelemetryConfiguration(); this._level = level; + const telemetryDetails = this.getTelemetryDetails(); + + // Loop through all loggers and update their level + this._telemetryLoggers.forEach(logger => { + logger.updateTelemetryEnablements(telemetryDetails.isUsageEnabled, telemetryDetails.isErrorsEnabled); + }); + if (this._oldTelemetryEnablement !== this.getTelemetryConfiguration()) { this._onDidChangeTelemetryEnabled.fire(this.getTelemetryConfiguration()); } @@ -61,12 +110,19 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { } } +export class ExtHostTelemetryOuptutChannel { + +} + export class ExtHostTelemetryLogger { private _appender: vscode.TelemetryAppender; private readonly _onDidChangeEnableStates = new Emitter(); private _telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }; private _apiObject: vscode.TelemetryLogger | undefined; - constructor(appender: vscode.TelemetryAppender, telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }) { + constructor( + appender: vscode.TelemetryAppender, + private readonly _commonProperties: Record, + telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }) { this._appender = appender; this._telemetryEnablements = { isUsageEnabled: telemetryEnablements.isUsageEnabled, isErrorsEnabled: telemetryEnablements.isErrorsEnabled }; } @@ -78,11 +134,28 @@ export class ExtHostTelemetryLogger { } } - logUsage(eventName: string, data?: Record): void { + mixInCommonPropsAndCleanData(data: Record): Record { + if (!this._appender.ignoreBuiltInCommonProperties) { + data = mixin(data, this._commonProperties); + } + if (this._appender.additionalCommonProperties) { + data = mixin(data, this._appender.additionalCommonProperties); + } + + return data; + } + + logUsage(eventName: string, data?: Record): void { + if (!this._telemetryEnablements.isUsageEnabled) { + return; + } this._appender.logEvent(eventName, data); } - logError(eventNameOrException: Error | string, data?: Record): void { + logError(eventNameOrException: Error | string, data?: Record): void { + if (!this._telemetryEnablements.isErrorsEnabled) { + return; + } if (typeof eventNameOrException === 'string') { this._appender.logEvent(eventNameOrException, data); } else { From 609ab9043d069daaca19250d5ec34081e7d8ccfb Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 12 Sep 2022 10:45:00 -0400 Subject: [PATCH 4/8] Implement telemetry logging extension host --- .../environment/common/environment.ts | 1 - .../environment/common/environmentService.ts | 1 - .../api/browser/mainThreadTelemetry.ts | 18 ---------- .../workbench/api/common/extHost.api.impl.ts | 6 ---- .../api/common/extHost.common.services.ts | 2 -- .../workbench/api/common/extHost.protocol.ts | 1 - .../workbench/api/common/extHostTelemetry.ts | 14 ++++---- .../api/common/extHostTelemetryLogService.ts | 34 ------------------- .../contrib/logs/common/logs.contribution.ts | 2 +- .../environment/browser/environmentService.ts | 5 +++ .../environment/common/environmentService.ts | 1 + .../electron-sandbox/environmentService.ts | 6 ++++ .../browser/webWorkerExtensionHost.ts | 1 + .../common/extensionHostProtocol.ts | 1 + .../common/extensionsApiProposals.ts | 1 - .../extensions/common/remoteExtensionHost.ts | 1 + .../localProcessExtensionHost.ts | 1 + .../vscode.proposed.telemetryLog.d.ts | 19 ----------- 18 files changed, 23 insertions(+), 92 deletions(-) delete mode 100644 src/vs/workbench/api/common/extHostTelemetryLogService.ts delete mode 100644 src/vscode-dts/vscode.proposed.telemetryLog.d.ts diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index faf55adc736c9..62775a8f61331 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -86,7 +86,6 @@ export interface IEnvironmentService { // --- telemetry disableTelemetry: boolean; telemetryLogResource: URI; - extensionTelemetryLogResource: URI; serviceMachineIdResource: URI; // --- Policy diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index d90cbc60fef99..90b24b9c924d7 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -226,7 +226,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get telemetryLogResource(): URI { return URI.file(join(this.logsPath, 'telemetry.log')); } - get extensionTelemetryLogResource(): URI { return URI.file(join(this.logsPath, 'extensionTelemetry.log')); } get disableTelemetry(): boolean { return !!this.args['disable-telemetry']; } @memoize diff --git a/src/vs/workbench/api/browser/mainThreadTelemetry.ts b/src/vs/workbench/api/browser/mainThreadTelemetry.ts index acb2067b0c8fd..1d5ba159d83f1 100644 --- a/src/vs/workbench/api/browser/mainThreadTelemetry.ts +++ b/src/vs/workbench/api/browser/mainThreadTelemetry.ts @@ -5,7 +5,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; @@ -19,26 +18,14 @@ export class MainThreadTelemetry extends Disposable implements MainThreadTelemet private static readonly _name = 'pluginHostTelemetry'; - private readonly _extensionTelemetryLog: ILogger; - constructor( extHostContext: IExtHostContext, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IProductService private readonly _productService: IProductService, - @ILoggerService loggerService: ILoggerService, ) { super(); - const logger = loggerService.getLogger(this._environmentService.extensionTelemetryLogResource); - if (logger) { - this._extensionTelemetryLog = this._register(logger); - } else { - this._extensionTelemetryLog = this._register(loggerService.createLogger(this._environmentService.extensionTelemetryLogResource)); - this._extensionTelemetryLog.info('Below are logs for extension telemetry events sent to the telemetry output channel API once the log level is set to trace.'); - this._extensionTelemetryLog.info('==========================================================='); - } - this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTelemetry); if (supportsTelemetry(this._productService, this._environmentService)) { @@ -67,11 +54,6 @@ export class MainThreadTelemetry extends Disposable implements MainThreadTelemet $publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): void { this.$publicLog(eventName, data as any); } - - $logTelemetryToOutputChannel(eventName: string, data: Record) { - this._extensionTelemetryLog.trace(eventName, data); - this._extensionTelemetryLog.flush(); - } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 6ea82f150dfea..32a5f281631a5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -92,7 +92,6 @@ import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; -import { IExtHostTelemetryLogService } from 'vs/workbench/api/common/extHostTelemetryLogService'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -123,7 +122,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostLoggerService = accessor.get(ILoggerService); const extHostLogService = accessor.get(ILogService); const extHostTunnelService = accessor.get(IExtHostTunnelService); - const extHostTelemetryLogService = accessor.get(IExtHostTelemetryLogService); const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); const extHostWindow = accessor.get(IExtHostWindow); const extHostSecretState = accessor.get(IExtHostSecretState); @@ -794,10 +792,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, get tabGroups(): vscode.TabGroups { return extHostEditorTabs.tabGroups; - }, - logTelemetryToOutputChannel(eventName: string, data: Record): void { - checkProposedApiEnabled(extension, 'telemetryLog'); - extHostTelemetryLogService.logToTelemetryOutputChannel(extension, eventName, data); } }; diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index 45ccc2baf3b62..cc6ff4889e7c9 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -27,7 +27,6 @@ import { ExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerServi import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostVariableResolverProviderService, IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService'; -import { ExtHostTelemetryLogService, IExtHostTelemetryLogService } from 'vs/workbench/api/common/extHostTelemetryLogService'; registerSingleton(ILoggerService, ExtHostLoggerService, true); registerSingleton(ILogService, ExtHostLogService, true); @@ -49,6 +48,5 @@ registerSingleton(IExtHostWindow, ExtHostWindow, false); registerSingleton(IExtHostWorkspace, ExtHostWorkspace, false); registerSingleton(IExtHostSecretState, ExtHostSecretState, false); registerSingleton(IExtHostTelemetry, ExtHostTelemetry, false); -registerSingleton(IExtHostTelemetryLogService, ExtHostTelemetryLogService, false); registerSingleton(IExtHostEditorTabs, ExtHostEditorTabs, false); registerSingleton(IExtHostVariableResolverProvider, ExtHostVariableResolverProviderService, false); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 98e4262551897..62d99da838799 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -598,7 +598,6 @@ export interface MainThreadStorageShape extends IDisposable { export interface MainThreadTelemetryShape extends IDisposable { $publicLog(eventName: string, data?: any): void; $publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): void; - $logTelemetryToOutputChannel(eventName: string, data: Record): void; } export interface MainThreadEditorInsetsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index 8f734a3239c98..92457bc1bcd77 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -6,17 +6,16 @@ import type * as vscode from 'vscode'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTelemetryShape, MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILoggerService } from 'vs/platform/log/common/log'; -import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; import { mixin } from 'vs/base/common/objects'; +import { URI } from 'vs/base/common/uri'; export class ExtHostTelemetry implements ExtHostTelemetryShape { private readonly _onDidChangeTelemetryEnabled = new Emitter(); @@ -29,16 +28,15 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { private _level: TelemetryLevel = TelemetryLevel.NONE; private _oldTelemetryEnablement: boolean | undefined; private readonly _telemetryLoggers = new Map(); - private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; constructor( - @IExtHostRpcService rpc: IExtHostRpcService, - @IExtHostFileSystemInfo extHostFileSystemInfo: IExtHostFileSystemInfo, @IExtHostInitDataService private readonly initData: IExtHostInitDataService, @ILoggerService loggerService: ILoggerService, ) { - //const log = loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); - this._mainThreadTelemetryProxy = rpc.getProxy(MainContext.MainThreadTelemetry); + console.log(this.initData.environment.extensionTelemetryLogResource); + const log = loggerService.createLogger(URI.revive(this.initData.environment.extensionTelemetryLogResource)); + log.info('Below are logs for extension telemetry events sent to the telemetry output channel API once the log level is set to trace.'); + log.info('==========================================================='); } getTelemetryConfiguration(): boolean { diff --git a/src/vs/workbench/api/common/extHostTelemetryLogService.ts b/src/vs/workbench/api/common/extHostTelemetryLogService.ts deleted file mode 100644 index e55fcb27b4c9f..0000000000000 --- a/src/vs/workbench/api/common/extHostTelemetryLogService.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; - -export interface IExtHostTelemetryLogService { - readonly _serviceBrand: undefined; - - logToTelemetryOutputChannel(extension: IExtensionDescription, eventName: string, data: Record): void; -} - -export const IExtHostTelemetryLogService = createDecorator('IExtHostTelemetryLogService'); - -export class ExtHostTelemetryLogService implements IExtHostTelemetryLogService { - - declare readonly _serviceBrand: undefined; - - private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape; - - constructor( - @IExtHostRpcService rpc: IExtHostRpcService, - ) { - this._telemetryShape = rpc.getProxy(extHostProtocol.MainContext.MainThreadTelemetry); - } - - public logToTelemetryOutputChannel(extension: IExtensionDescription, eventName: string, data: Record): void { - this._telemetryShape.$logTelemetryToOutputChannel(`${extension.identifier.value}/${eventName}`, data); - } -} diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index d4e9518e352f0..e169ef43dec23 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -44,7 +44,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { const registerTelemetryChannel = () => { if (supportsTelemetry(this.productService, this.environmentService) && this.logService.getLevel() === LogLevel.Trace) { this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), this.environmentService.telemetryLogResource); - this.registerLogChannel(Constants.extensionTelemetryLogChannelId, nls.localize('extensionTelemetryLog', "Extension Telemetry"), this.environmentService.extensionTelemetryLogResource); + this.registerLogChannel(Constants.extensionTelemetryLogChannelId, nls.localize('extensionTelemetryLog', "Extension Telemetry"), this.environmentService.extHostTelemetryLogFile); return true; } return false; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 372ac7bb16707..b4babc0fae69d 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -101,6 +101,11 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi @memoize get extHostLogsPath(): URI { return joinPath(this.logsHome, 'exthost'); } + @memoize + get extHostTelemetryLogFile(): URI { + return joinPath(this.extHostLogsPath, 'telemetry.log'); + } + private extensionHostDebugEnvironment: IExtensionHostDebugEnvironment | undefined = undefined; @memoize diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index de37a985f1762..c922bb638592a 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -25,6 +25,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { // --- Paths readonly logFile: URI; readonly extHostLogsPath: URI; + readonly extHostTelemetryLogFile: URI; // --- Extensions readonly extensionEnabledProposedApi?: string[]; diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index a18fa598d417f..1d1498d1c4aab 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { join } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; +import { joinPath } from 'vs/base/common/resources'; export const INativeWorkbenchEnvironmentService = refineServiceDecorator(IEnvironmentService); @@ -90,6 +91,11 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } + @memoize + get extHostTelemetryLogFile(): URI { + return joinPath(this.extHostLogsPath, 'telemetry.log'); + } + @memoize get webviewExternalEndpoint(): string { return `${Schemas.vscodeWebview}://{{uuid}}`; } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index eb70bb9ff17ab..b63f0c1f37d64 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -287,6 +287,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost appHost: this._productService.embedderIdentifier ?? (platform.isWeb ? 'web' : 'desktop'), appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, + extensionTelemetryLogResource: this._environmentService.extHostTelemetryLogFile, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 285ab4355c23e..f1bb941b4cace 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -45,6 +45,7 @@ export interface IEnvironment { appHost: string; appRoot?: URI; appLanguage: string; + extensionTelemetryLogResource: URI; appUriScheme: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index e88c10c0b70ba..fa2d239c5d7c7 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -57,7 +57,6 @@ export const allApiProposals = Object.freeze({ tabInputTextMerge: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts', taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', - telemetryLog: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetryLog.d.ts', telemetryLogger: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetryLogger.d.ts', terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 62eea683c1818..ae69e5945ea46 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -221,6 +221,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { appName: this._productService.nameLong, appHost: this._productService.embedderIdentifier || 'desktop', appUriScheme: this._productService.urlProtocol, + extensionTelemetryLogResource: this._environmentService.extHostTelemetryLogFile, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 8d240be767076..b5df3055356e2 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -444,6 +444,7 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { appName: this._productService.nameLong, appHost: this._productService.embedderIdentifier || 'desktop', appUriScheme: this._productService.urlProtocol, + extensionTelemetryLogResource: this._environmentService.extHostTelemetryLogFile, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vscode-dts/vscode.proposed.telemetryLog.d.ts b/src/vscode-dts/vscode.proposed.telemetryLog.d.ts deleted file mode 100644 index 962016a02d85c..0000000000000 --- a/src/vscode-dts/vscode.proposed.telemetryLog.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - export namespace window { - /** - * Logs a telemetry event to a shared extension output channel when the log level is set to trace. - * This is similar in function to cores' telemetry output channel that can be seen when log level is set to trace. - * Extension authors should only log to the output channel when sending telemetry. - * - * @param eventName The name of the telemetry event - * @param data The data associated with the telemetry event - */ - export function logTelemetryToOutputChannel(eventName: string, data: Record): void; - } -} From 1760521b532a7cd17269df1af10123d5bf59cd6f Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 13 Sep 2022 10:39:10 -0400 Subject: [PATCH 5/8] Add logging to exthost logger --- .../workbench/api/common/extHost.api.impl.ts | 5 ++- .../workbench/api/common/extHostTelemetry.ts | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8b12ede229515..e60d204b78f0e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -80,7 +80,7 @@ import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; -import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; @@ -322,8 +322,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTelemetry.onDidChangeTelemetryConfiguration; }, get isNewAppInstall() { - const installAge = Date.now() - new Date(initData.telemetryInfo.firstSessionDate).getTime(); - return isNaN(installAge) ? false : installAge < 1000 * 60 * 60 * 24; // install age is less than a day + return isNewAppInstall(initData.telemetryInfo.firstSessionDate); }, createTelemetryLogger(appender: vscode.TelemetryAppender): vscode.TelemetryLogger { checkProposedApiEnabled(extension, 'telemetry'); diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index 92457bc1bcd77..2b4f9e2603a1f 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ILoggerService } from 'vs/platform/log/common/log'; +import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -27,6 +27,7 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { private _productConfig: { usage: boolean; error: boolean } = { usage: true, error: true }; private _level: TelemetryLevel = TelemetryLevel.NONE; private _oldTelemetryEnablement: boolean | undefined; + private readonly _outputLogger: ILogger; private readonly _telemetryLoggers = new Map(); constructor( @@ -34,9 +35,9 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { @ILoggerService loggerService: ILoggerService, ) { console.log(this.initData.environment.extensionTelemetryLogResource); - const log = loggerService.createLogger(URI.revive(this.initData.environment.extensionTelemetryLogResource)); - log.info('Below are logs for extension telemetry events sent to the telemetry output channel API once the log level is set to trace.'); - log.info('==========================================================='); + this._outputLogger = loggerService.createLogger(URI.revive(this.initData.environment.extensionTelemetryLogResource)); + this._outputLogger.info('Below are logs for extension telemetry events sent to the telemetry output channel API once the log level is set to trace.'); + this._outputLogger.info('==========================================================='); } getTelemetryConfiguration(): boolean { @@ -53,7 +54,7 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { instantiateLogger(extension: IExtensionDescription, appender: vscode.TelemetryAppender) { const telemetryDetails = this.getTelemetryDetails(); - const logger = new ExtHostTelemetryLogger(appender, this.getBuiltInCommonProperties(extension), { isUsageEnabled: telemetryDetails.isUsageEnabled, isErrorsEnabled: telemetryDetails.isErrorsEnabled }); + const logger = new ExtHostTelemetryLogger(appender, extension, this._outputLogger, this.getBuiltInCommonProperties(extension), { isUsageEnabled: telemetryDetails.isUsageEnabled, isErrorsEnabled: telemetryDetails.isErrorsEnabled }); this._telemetryLoggers.set(extension.identifier.value, logger); return logger.apiTelemetryLogger; } @@ -72,7 +73,7 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { commonProperties['common.vscodemachineid'] = this.initData.telemetryInfo.machineId; commonProperties['common.vscodesessionid'] = this.initData.telemetryInfo.sessionId; commonProperties['common.vscodeversion'] = this.initData.version; - //commonProperties['common.isnewappinstall'] = this.initData.isNewAppInstall ? this.vscodeAPI.env.isNewAppInstall.toString() : "false"; + commonProperties['common.isnewappinstall'] = isNewAppInstall(this.initData.telemetryInfo.firstSessionDate); commonProperties['common.product'] = this.initData.environment.appHost; switch (this.initData.uiKind) { @@ -108,10 +109,6 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape { } } -export class ExtHostTelemetryOuptutChannel { - -} - export class ExtHostTelemetryLogger { private _appender: vscode.TelemetryAppender; private readonly _onDidChangeEnableStates = new Emitter(); @@ -119,6 +116,8 @@ export class ExtHostTelemetryLogger { private _apiObject: vscode.TelemetryLogger | undefined; constructor( appender: vscode.TelemetryAppender, + private readonly _extension: IExtensionDescription, + private readonly _logger: ILogger, private readonly _commonProperties: Record, telemetryEnablements: { isUsageEnabled: boolean; isErrorsEnabled: boolean }) { this._appender = appender; @@ -143,11 +142,18 @@ export class ExtHostTelemetryLogger { return data; } + private logEvent(eventName: string, data?: Record): void { + eventName = this._extension.identifier.value + '/' + eventName; + data = this.mixInCommonPropsAndCleanData(data || {}); + this._appender.logEvent(eventName, data); + this._logger.trace(eventName, data); + } + logUsage(eventName: string, data?: Record): void { if (!this._telemetryEnablements.isUsageEnabled) { return; } - this._appender.logEvent(eventName, data); + this.logEvent(eventName, data); } logError(eventNameOrException: Error | string, data?: Record): void { @@ -155,7 +161,7 @@ export class ExtHostTelemetryLogger { return; } if (typeof eventNameOrException === 'string') { - this._appender.logEvent(eventNameOrException, data); + this.logEvent(eventNameOrException, data); } else { this._appender.logException(eventNameOrException, data); } @@ -188,5 +194,10 @@ export class ExtHostTelemetryLogger { } } +export function isNewAppInstall(firstSessionDate: string): boolean { + const installAge = Date.now() - new Date(firstSessionDate).getTime(); + return isNaN(installAge) ? false : installAge < 1000 * 60 * 60 * 24; // install age is less than a day +} + export const IExtHostTelemetry = createDecorator('IExtHostTelemetry'); export interface IExtHostTelemetry extends ExtHostTelemetry, ExtHostTelemetryShape { } From 4d86f9be1042f084f7806bb6404e9a77f0ac2c65 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 13 Sep 2022 15:29:18 -0400 Subject: [PATCH 6/8] Extract telemetry cleaning --- .../telemetry/common/telemetryService.ts | 93 +-------------- .../telemetry/common/telemetryUtils.ts | 108 +++++++++++++++++- 2 files changed, 110 insertions(+), 91 deletions(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index bfe90f7fed7b8..a2c2152f7b8aa 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; -import { cloneAndChange, mixin } from 'vs/base/common/objects'; +import { mixin } from 'vs/base/common/objects'; import { MutableObservableValue } from 'vs/base/common/observableValue'; import { isWeb } from 'vs/base/common/platform'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -16,7 +16,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SECTION_ID, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; -import { getTelemetryLevel, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; +import { cleanData, getTelemetryLevel, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; export interface ITelemetryServiceConfig { appenders: ITelemetryAppender[]; @@ -117,12 +117,7 @@ export class TelemetryService implements ITelemetryService { data = mixin(data, this._experimentProperties); // (last) remove all PII from data - data = cloneAndChange(data, value => { - if (typeof value === 'string') { - return this._cleanupInfo(value, anonymizeFilePaths); - } - return undefined; - }); + data = cleanData(data as Record, this._cleanupPatterns); // Log to the appenders of sufficient level this._appenders.forEach(a => a.log(eventName, data)); @@ -153,88 +148,6 @@ export class TelemetryService implements ITelemetryService { publicLogError2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): Promise { return this.publicLogError(eventName, data as ITelemetryData); } - - private _anonymizeFilePaths(stack: string): string { - let updatedStack = stack; - - const cleanUpIndexes: [number, number][] = []; - for (const regexp of this._cleanupPatterns) { - while (true) { - const result = regexp.exec(stack); - if (!result) { - break; - } - cleanUpIndexes.push([result.index, regexp.lastIndex]); - } - } - - const nodeModulesRegex = /^[\\\/]?(node_modules|node_modules\.asar)[\\\/]/; - const fileRegex = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w-\._]+(\\\\|\\|\/))+[\w-\._]*/g; - let lastIndex = 0; - updatedStack = ''; - - while (true) { - const result = fileRegex.exec(stack); - if (!result) { - break; - } - // Anoynimize user file paths that do not need to be retained or cleaned up. - if (!nodeModulesRegex.test(result[0]) && cleanUpIndexes.every(([x, y]) => result.index < x || result.index >= y)) { - updatedStack += stack.substring(lastIndex, result.index) + ''; - lastIndex = fileRegex.lastIndex; - } - } - if (lastIndex < stack.length) { - updatedStack += stack.substr(lastIndex); - } - - return updatedStack; - } - - private _removePropertiesWithPossibleUserInfo(property: string): string { - // If for some reason it is undefined we skip it (this shouldn't be possible); - if (!property) { - return property; - } - - const value = property.toLowerCase(); - - const userDataRegexes = [ - { label: 'Google API Key', regex: /AIza[A-Za-z0-9_\\\-]{35}/ }, - { label: 'Slack Token', regex: /xox[pbar]\-[A-Za-z0-9]/ }, - { label: 'Generic Secret', regex: /(key|token|sig|secret|signature|password|passwd|pwd|android:value)[^a-zA-Z0-9]/ }, - { label: 'Email', regex: /@[a-zA-Z0-9-.]+/ } // Regex which matches @*.site - ]; - - // Check for common user data in the telemetry events - for (const secretRegex of userDataRegexes) { - if (secretRegex.regex.test(value)) { - return ``; - } - } - - return property; - } - - - private _cleanupInfo(property: string, anonymizeFilePaths?: boolean): string { - let updatedProperty = property; - - // anonymize file paths - if (anonymizeFilePaths) { - updatedProperty = this._anonymizeFilePaths(updatedProperty); - } - - // sanitize with configured cleanup patterns - for (const regexp of this._cleanupPatterns) { - updatedProperty = updatedProperty.replace(regexp, ''); - } - - // remove possible user info - updatedProperty = this._removePropertiesWithPossibleUserInfo(updatedProperty); - - return updatedProperty; - } } function getTelemetryLevelSettingDescription(): string { diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index cd0cb5009ecbb..258894a5fcf54 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { safeStringify } from 'vs/base/common/objects'; +import { cloneAndChange, safeStringify } from 'vs/base/common/objects'; import { staticObservableValue } from 'vs/base/common/observableValue'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -276,3 +276,109 @@ interface IPathEnvironment { export function getPiiPathsFromEnvironment(paths: IPathEnvironment): string[] { return [paths.appRoot, paths.extensionsPath, paths.userHome.fsPath, paths.tmpDir.fsPath, paths.userDataPath]; } + +//#region Telemetry Cleaning + +/** + * Cleans a given stack of possible paths + * @param stack The stack to sanitize + * @param cleanupPatterns Cleanup patterns to remove from the stack + * @returns The cleaned stack + */ +function anonymizeFilePaths(stack: string, cleanupPatterns: RegExp[]): string { + let updatedStack = stack; + + const cleanUpIndexes: [number, number][] = []; + for (const regexp of cleanupPatterns) { + while (true) { + const result = regexp.exec(stack); + if (!result) { + break; + } + cleanUpIndexes.push([result.index, regexp.lastIndex]); + } + } + + const nodeModulesRegex = /^[\\\/]?(node_modules|node_modules\.asar)[\\\/]/; + const fileRegex = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w-\._]+(\\\\|\\|\/))+[\w-\._]*/g; + let lastIndex = 0; + updatedStack = ''; + + while (true) { + const result = fileRegex.exec(stack); + if (!result) { + break; + } + // Anoynimize user file paths that do not need to be retained or cleaned up. + if (!nodeModulesRegex.test(result[0]) && cleanUpIndexes.every(([x, y]) => result.index < x || result.index >= y)) { + updatedStack += stack.substring(lastIndex, result.index) + ''; + lastIndex = fileRegex.lastIndex; + } + } + if (lastIndex < stack.length) { + updatedStack += stack.substr(lastIndex); + } + + return updatedStack; +} + +/** + * Attempts to remove commonly leaked PII + * @param property The property which will be removed if it contains user data + * @returns The new value for the property + */ +function removePropertiesWithPossibleUserInfo(property: string): string { + // If for some reason it is undefined we skip it (this shouldn't be possible); + if (!property) { + return property; + } + + const value = property.toLowerCase(); + + const userDataRegexes = [ + { label: 'Google API Key', regex: /AIza[A-Za-z0-9_\\\-]{35}/ }, + { label: 'Slack Token', regex: /xox[pbar]\-[A-Za-z0-9]/ }, + { label: 'Generic Secret', regex: /(key|token|sig|secret|signature|password|passwd|pwd|android:value)[^a-zA-Z0-9]/ }, + { label: 'Email', regex: /@[a-zA-Z0-9-.]+/ } // Regex which matches @*.site + ]; + + // Check for common user data in the telemetry events + for (const secretRegex of userDataRegexes) { + if (secretRegex.regex.test(value)) { + return ``; + } + } + + return property; +} + + +/** + * Does a best possible effort to clean a data object from any possible PII. + * @param data The data object to clean + * @param paths Any additional patterns that should be removed from the data set + * @returns A new object with the PII removed + */ +export function cleanData(data: Record, cleanUpPatterns: RegExp[]): Record { + return cloneAndChange(data, value => { + // We only know how to clean strings + if (typeof value === 'string') { + let updatedProperty = value; + // First we anonymize any possible file paths + updatedProperty = anonymizeFilePaths(updatedProperty, cleanUpPatterns); + + // Then we do a simple regex replace with the defined patterns + for (const regexp of cleanUpPatterns) { + updatedProperty = updatedProperty.replace(regexp, ''); + } + + // Lastly, remove commonly leaked PII + updatedProperty = removePropertiesWithPossibleUserInfo(updatedProperty); + + return updatedProperty; + } + return undefined; + }); +} + +//#endregion From 77e070df23dfd15a47a5f48693a97d1de2604316 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 14 Sep 2022 09:55:33 -0400 Subject: [PATCH 7/8] Add data cleaning --- src/vs/workbench/api/common/extHostTelemetry.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTelemetry.ts b/src/vs/workbench/api/common/extHostTelemetry.ts index 2b4f9e2603a1f..47940f571d58a 100644 --- a/src/vs/workbench/api/common/extHostTelemetry.ts +++ b/src/vs/workbench/api/common/extHostTelemetry.ts @@ -13,7 +13,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; -import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; +import { cleanData, cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; import { mixin } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; @@ -139,6 +139,8 @@ export class ExtHostTelemetryLogger { data = mixin(data, this._appender.additionalCommonProperties); } + data = cleanData(data, []); + return data; } @@ -163,6 +165,7 @@ export class ExtHostTelemetryLogger { if (typeof eventNameOrException === 'string') { this.logEvent(eventNameOrException, data); } else { + // TODO @lramos15, implement cleaning for and logging for this case this._appender.logException(eventNameOrException, data); } } From 70bec10a691a37dc9a32a494fdcd7ddcbcf789aa Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 11 Oct 2022 11:15:45 -0400 Subject: [PATCH 8/8] Update email regex --- src/vs/platform/telemetry/common/telemetryUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 258894a5fcf54..1f2044b947e7b 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -339,7 +339,7 @@ function removePropertiesWithPossibleUserInfo(property: string): string { { label: 'Google API Key', regex: /AIza[A-Za-z0-9_\\\-]{35}/ }, { label: 'Slack Token', regex: /xox[pbar]\-[A-Za-z0-9]/ }, { label: 'Generic Secret', regex: /(key|token|sig|secret|signature|password|passwd|pwd|android:value)[^a-zA-Z0-9]/ }, - { label: 'Email', regex: /@[a-zA-Z0-9-.]+/ } // Regex which matches @*.site + { label: 'Email', regex: /@[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+/ } // Regex which matches @*.site ]; // Check for common user data in the telemetry events