From 0edb88f3b556ce88f222d5bf71de4d2d958a4476 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 17 Jun 2022 15:10:04 -0400 Subject: [PATCH] 1DS appender for the web (#152489) * 1ds web appender * Start testing web --- .eslintrc.json | 6 +- .../platform/telemetry/browser/1dsAppender.ts | 128 ++++++++++++++++++ .../telemetry/browser/telemetryService.ts | 16 ++- 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 src/vs/platform/telemetry/browser/1dsAppender.ts diff --git a/.eslintrc.json b/.eslintrc.json index 3e1b349becc..4d49a16fbf0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -227,8 +227,6 @@ "@vscode/ripgrep", "@vscode/iconv-lite-umd", "applicationinsights", - "@microsoft/1ds-core-js", - "@microsoft/1ds-post-js", "assert", "child_process", "console", @@ -332,7 +330,9 @@ "vs/base/~", "vs/base/parts/*/~", "vs/platform/*/~", - "tas-client-umd" // node module allowed even in /common/ + "tas-client-umd", // node module allowed even in /common/ + "@microsoft/1ds-core-js",// node module allowed even in /common/ + "@microsoft/1ds-post-js" // node module allowed even in /common/ ] }, { diff --git a/src/vs/platform/telemetry/browser/1dsAppender.ts b/src/vs/platform/telemetry/browser/1dsAppender.ts new file mode 100644 index 00000000000..eb91e96acd3 --- /dev/null +++ b/src/vs/platform/telemetry/browser/1dsAppender.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-core-js'; +import type { PostChannel } from '@microsoft/1ds-post-js'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { mixin } from 'vs/base/common/objects'; +import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; + +const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0'; + +async function getClient(instrumentationKey: string): Promise { + const oneDs = await import('@microsoft/1ds-core-js'); + const postPlugin = await import('@microsoft/1ds-post-js'); + const appInsightsCore = new oneDs.AppInsightsCore(); + const collectorChannelPlugin: PostChannel = new postPlugin.PostChannel(); + // Configure the app insights core to send to collector++ and disable logging of debug info + const coreConfig: IExtendedConfiguration = { + instrumentationKey, + endpointUrl, + loggingLevelTelemetry: 0, + loggingLevelConsole: 0, + disableCookiesUsage: true, + disableDbgExt: true, + disableInstrumentationKeyValidation: true, + channels: [[ + collectorChannelPlugin + ]] + }; + + appInsightsCore.initialize(coreConfig, []); + + appInsightsCore.addTelemetryInitializer((envelope) => { + if (envelope.tags) { + // Sets it to be internal only based on Windows UTC flagging + envelope.tags['utc.flags'] = 0x0000811ECD; + } + }); + + return appInsightsCore; +} + +// TODO @lramos15 maybe make more in line with src/vs/platform/telemetry/browser/appInsightsAppender.ts with caching support +export class OneDataSystemWebAppender implements ITelemetryAppender { + + private _aiCoreOrKey: AppInsightsCore | string | undefined; + private _asyncAiCore: Promise | null; + + constructor( + private _eventPrefix: string, + private _defaultData: { [key: string]: any } | null, + iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing + ) { + if (!this._defaultData) { + this._defaultData = Object.create(null); + } + + if (typeof iKeyOrClientFactory === 'function') { + this._aiCoreOrKey = iKeyOrClientFactory(); + } else { + this._aiCoreOrKey = iKeyOrClientFactory; + } + this._asyncAiCore = null; + + // If we cannot fetch the endpoint it means it is down and we should not send any telemetry. + // This is most likely due to ad blockers + fetch(endpointUrl, { method: 'POST' }).catch(err => { + this._aiCoreOrKey = undefined; + }); + } + + private _withAIClient(callback: (aiCore: AppInsightsCore) => void): void { + if (!this._aiCoreOrKey) { + return; + } + + if (typeof this._aiCoreOrKey !== 'string') { + callback(this._aiCoreOrKey); + return; + } + + if (!this._asyncAiCore) { + this._asyncAiCore = getClient(this._aiCoreOrKey); + } + + this._asyncAiCore.then( + (aiClient) => { + callback(aiClient); + }, + (err) => { + onUnexpectedError(err); + console.error(err); + } + ); + } + + log(eventName: string, data?: any): void { + if (!this._aiCoreOrKey) { + return; + } + data = mixin(data, this._defaultData); + data = validateTelemetryData(data); + + try { + this._withAIClient((aiClient) => aiClient.track({ + name: this._eventPrefix + '/' + eventName, + data: { ...data.properties, ...data.measurements }, + + })); + } catch { } + } + + flush(): Promise { + if (this._aiCoreOrKey) { + return new Promise(resolve => { + this._withAIClient((aiClient) => { + aiClient.unload(true, () => { + this._aiCoreOrKey = undefined; + resolve(undefined); + }); + }); + }); + } + return Promise.resolve(undefined); + } +} diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index bd161f54f33..8e86627e42f 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -10,6 +10,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender'; import { WebAppInsightsAppender } from 'vs/platform/telemetry/browser/appInsightsAppender'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; @@ -37,11 +38,20 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey) { + if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey && productService.aiConfig?.ariaKey) { // If remote server is present send telemetry through that, else use the client side appender - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey); + const internalTesting = configurationService.getValue('telemetry.internalTesting'); + const appenders = []; + if (internalTesting) { + const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig?.ariaKey); + appenders.push(telemetryProvider); + } else { + const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey); + appenders.push(telemetryProvider); + } + appenders.push(new TelemetryLogAppender(loggerService, environmentService)); const config: ITelemetryServiceConfig = { - appenders: [telemetryProvider, new TelemetryLogAppender(loggerService, environmentService)], + appenders, commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), sendErrorTelemetry: this.sendErrorTelemetry, };