Path: blob/main/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts
5240 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { URI } from '../../../../base/common/uri.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';9import { DEFAULT_LOG_LEVEL, LogLevel } from '../../../../platform/log/common/log.js';10import { TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';11import { TestTelemetryLoggerService } from '../../../../platform/telemetry/test/common/telemetryLogAppender.test.js';12import { IExtHostInitDataService } from '../../common/extHostInitDataService.js';13import { ExtHostTelemetry, ExtHostTelemetryLogger } from '../../common/extHostTelemetry.js';14import { IEnvironment } from '../../../services/extensions/common/extensionHostProtocol.js';15import { mock } from '../../../test/common/workbenchTestServices.js';16import type { TelemetryLoggerOptions, TelemetrySender } from 'vscode';1718interface TelemetryLoggerSpy {19dataArr: any[];20exceptionArr: any[];21flushCalled: boolean;22}2324suite('ExtHostTelemetry', function () {25const store = ensureNoDisposablesAreLeakedInTestSuite();2627const mockEnvironment: IEnvironment = {28isExtensionDevelopmentDebug: false,29extensionDevelopmentLocationURI: undefined,30extensionTestsLocationURI: undefined,31appRoot: undefined,32appName: 'test',33isExtensionTelemetryLoggingOnly: false,34appHost: 'test',35appLanguage: 'en',36globalStorageHome: URI.parse('fake'),37workspaceStorageHome: URI.parse('fake'),38appUriScheme: 'test',39};4041const mockTelemetryInfo = {42firstSessionDate: '2020-01-01T00:00:00.000Z',43sessionId: 'test',44machineId: 'test',45sqmId: 'test',46devDeviceId: 'test'47};4849const mockRemote = {50authority: 'test',51isRemote: false,52connectionData: null53};5455const mockExtensionIdentifier: IExtensionDescription = {56identifier: new ExtensionIdentifier('test-extension'),57targetPlatform: TargetPlatform.UNIVERSAL,58isBuiltin: true,59isUserBuiltin: true,60isUnderDevelopment: true,61name: 'test-extension',62publisher: 'vscode',63version: '1.0.0',64engines: { vscode: '*' },65extensionLocation: URI.parse('fake'),66enabledApiProposals: undefined,67preRelease: false,68};6970const createExtHostTelemetry = () => {71const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {72override environment: IEnvironment = mockEnvironment;73override telemetryInfo = mockTelemetryInfo;74override remote = mockRemote;75}, new TestTelemetryLoggerService(DEFAULT_LOG_LEVEL));76store.add(extensionTelemetry);77extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });78return extensionTelemetry;79};8081const createLogger = (functionSpy: TelemetryLoggerSpy, extHostTelemetry?: ExtHostTelemetry, options?: TelemetryLoggerOptions) => {82const extensionTelemetry = extHostTelemetry ?? createExtHostTelemetry();83// This is the appender which the extension would contribute84const appender: TelemetrySender = {85sendEventData: (eventName: string, data) => {86functionSpy.dataArr.push({ eventName, data });87},88sendErrorData: (exception, data) => {89functionSpy.exceptionArr.push({ exception, data });90},91flush: () => {92functionSpy.flushCalled = true;93}94};9596if (extHostTelemetry) {97store.add(extHostTelemetry);98}99100const logger = extensionTelemetry.instantiateLogger(mockExtensionIdentifier, appender, options);101store.add(logger);102return logger;103};104105test('Validate sender instances', function () {106// eslint-disable-next-line local/code-no-any-casts107assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>null));108// eslint-disable-next-line local/code-no-any-casts109assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>1));110// eslint-disable-next-line local/code-no-any-casts111assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>{}));112assert.throws(() => {113// eslint-disable-next-line local/code-no-any-casts114ExtHostTelemetryLogger.validateSender(<any>{115sendErrorData: () => { },116sendEventData: true117});118});119assert.throws(() => {120// eslint-disable-next-line local/code-no-any-casts121ExtHostTelemetryLogger.validateSender(<any>{122sendErrorData: 123,123sendEventData: () => { },124});125});126assert.throws(() => {127// eslint-disable-next-line local/code-no-any-casts128ExtHostTelemetryLogger.validateSender(<any>{129sendErrorData: () => { },130sendEventData: () => { },131flush: true132});133});134});135136test('Ensure logger gets proper telemetry level during initialization', function () {137const extensionTelemetry = createExtHostTelemetry();138let config = extensionTelemetry.getTelemetryDetails();139assert.strictEqual(config.isCrashEnabled, true);140assert.strictEqual(config.isUsageEnabled, true);141assert.strictEqual(config.isErrorsEnabled, true);142143// Initialize would never be called twice, but this is just for testing144extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.ERROR, true, { usage: true, error: true });145config = extensionTelemetry.getTelemetryDetails();146assert.strictEqual(config.isCrashEnabled, true);147assert.strictEqual(config.isUsageEnabled, false);148assert.strictEqual(config.isErrorsEnabled, true);149150extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.CRASH, true, { usage: true, error: true });151config = extensionTelemetry.getTelemetryDetails();152assert.strictEqual(config.isCrashEnabled, true);153assert.strictEqual(config.isUsageEnabled, false);154assert.strictEqual(config.isErrorsEnabled, false);155156extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: false, error: true });157config = extensionTelemetry.getTelemetryDetails();158assert.strictEqual(config.isCrashEnabled, true);159assert.strictEqual(config.isUsageEnabled, false);160assert.strictEqual(config.isErrorsEnabled, true);161extensionTelemetry.dispose();162});163164test('Simple log event to TelemetryLogger', function () {165const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };166167const logger = createLogger(functionSpy);168169logger.logUsage('test-event', { 'test-data': 'test-data' });170assert.strictEqual(functionSpy.dataArr.length, 1);171assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);172assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');173174logger.logUsage('test-event', { 'test-data': 'test-data' });175assert.strictEqual(functionSpy.dataArr.length, 2);176177logger.logError('test-event', { 'test-data': 'test-data' });178assert.strictEqual(functionSpy.dataArr.length, 3);179180logger.logError(new Error('test-error'), { 'test-data': 'test-data' });181assert.strictEqual(functionSpy.dataArr.length, 3);182assert.strictEqual(functionSpy.exceptionArr.length, 1);183184185// Assert not flushed186assert.strictEqual(functionSpy.flushCalled, false);187188// Call flush and assert that flush occurs189logger.dispose();190assert.strictEqual(functionSpy.flushCalled, true);191192});193194test('Simple log event to TelemetryLogger with options', function () {195const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };196197const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });198199logger.logUsage('test-event', { 'test-data': 'test-data' });200assert.strictEqual(functionSpy.dataArr.length, 1);201assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);202assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');203assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');204205logger.logUsage('test-event', { 'test-data': 'test-data' });206assert.strictEqual(functionSpy.dataArr.length, 2);207208logger.logError('test-event', { 'test-data': 'test-data' });209assert.strictEqual(functionSpy.dataArr.length, 3);210211logger.logError(new Error('test-error'), { 'test-data': 'test-data' });212assert.strictEqual(functionSpy.dataArr.length, 3);213assert.strictEqual(functionSpy.exceptionArr.length, 1);214215216// Assert not flushed217assert.strictEqual(functionSpy.flushCalled, false);218219// Call flush and assert that flush occurs220logger.dispose();221assert.strictEqual(functionSpy.flushCalled, true);222223});224225test('Log error should get common properties #193205', function () {226const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };227228const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });229logger.logError(new Error('Test error'));230assert.strictEqual(functionSpy.exceptionArr.length, 1);231assert.strictEqual(functionSpy.exceptionArr[0].data['common.foo'], 'bar');232assert.strictEqual(functionSpy.exceptionArr[0].data['common.product'], 'test');233234logger.logError('test-error-event');235assert.strictEqual(functionSpy.dataArr.length, 1);236assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');237assert.strictEqual(functionSpy.dataArr[0].data['common.product'], 'test');238239logger.logError('test-error-event', { 'test-data': 'test-data' });240assert.strictEqual(functionSpy.dataArr.length, 2);241assert.strictEqual(functionSpy.dataArr[1].data['common.foo'], 'bar');242assert.strictEqual(functionSpy.dataArr[1].data['common.product'], 'test');243244logger.logError('test-error-event', { properties: { 'test-data': 'test-data' } });245assert.strictEqual(functionSpy.dataArr.length, 3);246assert.strictEqual(functionSpy.dataArr[2].data.properties['common.foo'], 'bar');247assert.strictEqual(functionSpy.dataArr[2].data.properties['common.product'], 'test');248249logger.dispose();250assert.strictEqual(functionSpy.flushCalled, true);251});252253254test('Ensure logger properly cleans PII', function () {255const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };256257const logger = createLogger(functionSpy);258259// Log an event with a bunch of PII, this should all get cleaned out260logger.logUsage('test-event', {261'fake-password': 'pwd=123',262'fake-email': '[email protected]',263'fake-token': 'token=123',264'fake-slack-token': 'xoxp-123',265'fake-path': '/Users/username/.vscode/extensions',266});267268assert.strictEqual(functionSpy.dataArr.length, 1);269assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);270assert.strictEqual(functionSpy.dataArr[0].data['fake-password'], '<REDACTED: Generic Secret>');271assert.strictEqual(functionSpy.dataArr[0].data['fake-email'], '<REDACTED: Email>');272assert.strictEqual(functionSpy.dataArr[0].data['fake-token'], '<REDACTED: Generic Secret>');273assert.strictEqual(functionSpy.dataArr[0].data['fake-slack-token'], '<REDACTED: Slack Token>');274assert.strictEqual(functionSpy.dataArr[0].data['fake-path'], '<REDACTED: user-file-path>');275});276277test('Ensure output channel is logged to', function () {278279// Have to re-duplicate code here because I the logger service isn't exposed in the simple setup functions280const loggerService = new TestTelemetryLoggerService(LogLevel.Trace);281const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {282override environment: IEnvironment = mockEnvironment;283override telemetryInfo = mockTelemetryInfo;284override remote = mockRemote;285}, loggerService);286extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });287288const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };289290const logger = createLogger(functionSpy, extensionTelemetry);291292// Ensure headers are logged on instantiation293assert.strictEqual(loggerService.createLogger().logs.length, 0);294295logger.logUsage('test-event', { 'test-data': 'test-data' });296// Initial header is logged then the event297assert.strictEqual(loggerService.createLogger().logs.length, 1);298assert.ok(loggerService.createLogger().logs[0].startsWith('test-extension/test-event'));299});300});301302303