Path: blob/main/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts
3296 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 () {106assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>null));107assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>1));108assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>{}));109assert.throws(() => {110ExtHostTelemetryLogger.validateSender(<any>{111sendErrorData: () => { },112sendEventData: true113});114});115assert.throws(() => {116ExtHostTelemetryLogger.validateSender(<any>{117sendErrorData: 123,118sendEventData: () => { },119});120});121assert.throws(() => {122ExtHostTelemetryLogger.validateSender(<any>{123sendErrorData: () => { },124sendEventData: () => { },125flush: true126});127});128});129130test('Ensure logger gets proper telemetry level during initialization', function () {131const extensionTelemetry = createExtHostTelemetry();132let config = extensionTelemetry.getTelemetryDetails();133assert.strictEqual(config.isCrashEnabled, true);134assert.strictEqual(config.isUsageEnabled, true);135assert.strictEqual(config.isErrorsEnabled, true);136137// Initialize would never be called twice, but this is just for testing138extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.ERROR, true, { usage: true, error: true });139config = extensionTelemetry.getTelemetryDetails();140assert.strictEqual(config.isCrashEnabled, true);141assert.strictEqual(config.isUsageEnabled, false);142assert.strictEqual(config.isErrorsEnabled, true);143144extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.CRASH, true, { usage: true, error: true });145config = extensionTelemetry.getTelemetryDetails();146assert.strictEqual(config.isCrashEnabled, true);147assert.strictEqual(config.isUsageEnabled, false);148assert.strictEqual(config.isErrorsEnabled, false);149150extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: false, error: true });151config = extensionTelemetry.getTelemetryDetails();152assert.strictEqual(config.isCrashEnabled, true);153assert.strictEqual(config.isUsageEnabled, false);154assert.strictEqual(config.isErrorsEnabled, true);155extensionTelemetry.dispose();156});157158test('Simple log event to TelemetryLogger', function () {159const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };160161const logger = createLogger(functionSpy);162163logger.logUsage('test-event', { 'test-data': 'test-data' });164assert.strictEqual(functionSpy.dataArr.length, 1);165assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);166assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');167168logger.logUsage('test-event', { 'test-data': 'test-data' });169assert.strictEqual(functionSpy.dataArr.length, 2);170171logger.logError('test-event', { 'test-data': 'test-data' });172assert.strictEqual(functionSpy.dataArr.length, 3);173174logger.logError(new Error('test-error'), { 'test-data': 'test-data' });175assert.strictEqual(functionSpy.dataArr.length, 3);176assert.strictEqual(functionSpy.exceptionArr.length, 1);177178179// Assert not flushed180assert.strictEqual(functionSpy.flushCalled, false);181182// Call flush and assert that flush occurs183logger.dispose();184assert.strictEqual(functionSpy.flushCalled, true);185186});187188test('Simple log event to TelemetryLogger with options', function () {189const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };190191const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });192193logger.logUsage('test-event', { 'test-data': 'test-data' });194assert.strictEqual(functionSpy.dataArr.length, 1);195assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);196assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');197assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');198199logger.logUsage('test-event', { 'test-data': 'test-data' });200assert.strictEqual(functionSpy.dataArr.length, 2);201202logger.logError('test-event', { 'test-data': 'test-data' });203assert.strictEqual(functionSpy.dataArr.length, 3);204205logger.logError(new Error('test-error'), { 'test-data': 'test-data' });206assert.strictEqual(functionSpy.dataArr.length, 3);207assert.strictEqual(functionSpy.exceptionArr.length, 1);208209210// Assert not flushed211assert.strictEqual(functionSpy.flushCalled, false);212213// Call flush and assert that flush occurs214logger.dispose();215assert.strictEqual(functionSpy.flushCalled, true);216217});218219test('Log error should get common properties #193205', function () {220const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };221222const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });223logger.logError(new Error('Test error'));224assert.strictEqual(functionSpy.exceptionArr.length, 1);225assert.strictEqual(functionSpy.exceptionArr[0].data['common.foo'], 'bar');226assert.strictEqual(functionSpy.exceptionArr[0].data['common.product'], 'test');227228logger.logError('test-error-event');229assert.strictEqual(functionSpy.dataArr.length, 1);230assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');231assert.strictEqual(functionSpy.dataArr[0].data['common.product'], 'test');232233logger.logError('test-error-event', { 'test-data': 'test-data' });234assert.strictEqual(functionSpy.dataArr.length, 2);235assert.strictEqual(functionSpy.dataArr[1].data['common.foo'], 'bar');236assert.strictEqual(functionSpy.dataArr[1].data['common.product'], 'test');237238logger.logError('test-error-event', { properties: { 'test-data': 'test-data' } });239assert.strictEqual(functionSpy.dataArr.length, 3);240assert.strictEqual(functionSpy.dataArr[2].data.properties['common.foo'], 'bar');241assert.strictEqual(functionSpy.dataArr[2].data.properties['common.product'], 'test');242243logger.dispose();244assert.strictEqual(functionSpy.flushCalled, true);245});246247248test('Ensure logger properly cleans PII', function () {249const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };250251const logger = createLogger(functionSpy);252253// Log an event with a bunch of PII, this should all get cleaned out254logger.logUsage('test-event', {255'fake-password': 'pwd=123',256'fake-email': '[email protected]',257'fake-token': 'token=123',258'fake-slack-token': 'xoxp-123',259'fake-path': '/Users/username/.vscode/extensions',260});261262assert.strictEqual(functionSpy.dataArr.length, 1);263assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);264assert.strictEqual(functionSpy.dataArr[0].data['fake-password'], '<REDACTED: Generic Secret>');265assert.strictEqual(functionSpy.dataArr[0].data['fake-email'], '<REDACTED: Email>');266assert.strictEqual(functionSpy.dataArr[0].data['fake-token'], '<REDACTED: Generic Secret>');267assert.strictEqual(functionSpy.dataArr[0].data['fake-slack-token'], '<REDACTED: Slack Token>');268assert.strictEqual(functionSpy.dataArr[0].data['fake-path'], '<REDACTED: user-file-path>');269});270271test('Ensure output channel is logged to', function () {272273// Have to re-duplicate code here because I the logger service isn't exposed in the simple setup functions274const loggerService = new TestTelemetryLoggerService(LogLevel.Trace);275const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {276override environment: IEnvironment = mockEnvironment;277override telemetryInfo = mockTelemetryInfo;278override remote = mockRemote;279}, loggerService);280extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });281282const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };283284const logger = createLogger(functionSpy, extensionTelemetry);285286// Ensure headers are logged on instantiation287assert.strictEqual(loggerService.createLogger().logs.length, 0);288289logger.logUsage('test-event', { 'test-data': 'test-data' });290// Initial header is logged then the event291assert.strictEqual(loggerService.createLogger().logs.length, 1);292assert.ok(loggerService.createLogger().logs[0].startsWith('test-extension/test-event'));293});294});295296297