Switch to 1DS endpoint (#20769)

* Update to 1DS client

* remove product config

* Update ai keys

* use our own event prefix

* re-enable telemetry

* Update distro and remove default enableTelemetry

* distro

* Remove asimovKey references

* add comment

* distro

* distro

* distro

* add files that break precommit hook

* cleanup/fixes

* distro

* distro + event prefix update

* distro

* more
This commit is contained in:
Charles Gagnon
2022-11-03 08:45:06 -07:00
committed by GitHub
parent ca78491238
commit fb05c4304e
54 changed files with 795 additions and 462 deletions

View File

@@ -103,7 +103,7 @@ export interface IProductConfiguration {
readonly enableTelemetry?: boolean;
readonly openToWelcomeMainPage?: boolean;
readonly aiConfig?: {
readonly asimovKey: string;
readonly ariaKey: string;
};
readonly sendASmile?: {

View File

@@ -63,8 +63,7 @@ import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, isInternalTelemetry, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService';
import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
@@ -91,6 +90,7 @@ import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from '
import { SharedProcessTunnelService } from 'vs/platform/tunnel/node/sharedProcessTunnelService';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
import { isLinux } from 'vs/base/common/platform';
@@ -263,16 +263,15 @@ class SharedProcessMain extends Disposable {
// Telemetry
let telemetryService: ITelemetryService;
const appenders: ITelemetryAppender[] = [];
const internalTelemetry = isInternalTelemetry(productService, configurationService);
if (supportsTelemetry(productService, environmentService)) {
const logAppender = new TelemetryLogAppender(loggerService, environmentService);
appenders.push(logAppender);
const { installSourcePath } = environmentService;
// Application Insights
if (productService.aiConfig && productService.aiConfig.asimovKey) {
const appInsightsAppender = new AppInsightsAppender('adsworkbench', null, productService.aiConfig.asimovKey); // {{SQL CARBON EDIT}} Use our own event prefix
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
appenders.push(appInsightsAppender);
if (productService.aiConfig?.ariaKey) {
const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'adsworkbench', null, productService.aiConfig.ariaKey); // {{SQL CARBON EDIT}} Use our own event prefix
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data
appenders.push(collectorAppender);
}
telemetryService = new TelemetryService({

View File

@@ -46,8 +46,8 @@ import { RequestService } from 'vs/platform/request/node/requestService';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { supportsTelemetry, NullTelemetryService, isInternalTelemetry, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
@@ -96,7 +96,7 @@ class CliMain extends Disposable {
});
}
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
private async initServices(): Promise<[IInstantiationService, OneDataSystemAppender[]]> {
const services = new ServiceCollection();
// Product
@@ -154,10 +154,11 @@ class CliMain extends Disposable {
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
// Telemetry
const appenders: AppInsightsAppender[] = [];
const appenders: OneDataSystemAppender[] = [];
const isInternal = isInternalTelemetry(productService, configurationService);
if (supportsTelemetry(productService, environmentService)) {
if (productService.aiConfig && productService.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender('adsworkbench', null, productService.aiConfig.asimovKey)); // {{SQL CARBON EDIT}} Use our own event prefix
if (productService.aiConfig && productService.aiConfig.ariaKey) {
appenders.push(new OneDataSystemAppender(isInternal, 'adsworkbench', null, productService.aiConfig.ariaKey)); // {{SQL CARBON EDIT}} Use our own event prefix
}
const { installSourcePath } = environmentService;

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractOneDataSystemAppender, IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender';
export class OneDataSystemWebAppender extends AbstractOneDataSystemAppender {
constructor(
isInternalTelemetry: boolean,
eventPrefix: string,
defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing
) {
super(isInternalTelemetry, eventPrefix, defaultData, iKeyOrClientFactory);
// 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(this.endPointUrl, { method: 'POST' }).catch(err => {
this._aiCoreOrKey = undefined;
});
}
}

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { IExtendedConfiguration, IExtendedTelemetryItem, ITelemetryItem, ITelemetryUnloadState } from '@microsoft/1ds-core-js';
import type { IChannelConfiguration, IXHROverride, 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';
// Interface type which is a subset of @microsoft/1ds-core-js AppInsightsCore.
// Allows us to more easily build mock objects for testing as the interface is quite large and we only need a few properties.
export interface IAppInsightsCore {
pluginVersionString: string;
track(item: ITelemetryItem | IExtendedTelemetryItem): void;
unload(isAsync: boolean, unloadComplete: (unloadState: ITelemetryUnloadState) => void): void;
}
const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0';
async function getClient(instrumentationKey: string, addInternalFlag?: boolean, xhrOverride?: IXHROverride): Promise<IAppInsightsCore> {
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
]]
};
if (xhrOverride) {
coreConfig.extensionConfig = {};
// Configure the channel to use a XHR Request override since it's not available in node
const channelConfig: IChannelConfiguration = {
alwaysUseXhrOverride: true,
httpXHROverride: xhrOverride
};
coreConfig.extensionConfig[collectorChannelPlugin.identifier] = channelConfig;
}
appInsightsCore.initialize(coreConfig, []);
appInsightsCore.addTelemetryInitializer((envelope) => {
if (addInternalFlag) {
envelope['ext'] = envelope['ext'] ?? {};
envelope['ext']['utc'] = envelope['ext']['utc'] ?? {};
// Sets it to be internal only based on Windows UTC flagging
envelope['ext']['utc']['flags'] = 0x0000811ECD;
}
});
return appInsightsCore;
}
// TODO @lramos15 maybe make more in line with src/vs/platform/telemetry/browser/appInsightsAppender.ts with caching support
export abstract class AbstractOneDataSystemAppender implements ITelemetryAppender {
protected _aiCoreOrKey: IAppInsightsCore | string | undefined;
private _asyncAiCore: Promise<IAppInsightsCore> | null;
protected readonly endPointUrl = endpointUrl;
constructor(
private readonly _isInternalTelemetry: boolean,
private _eventPrefix: string,
private _defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing
private _xhrOverride?: IXHROverride
) {
if (!this._defaultData) {
this._defaultData = {};
}
if (typeof iKeyOrClientFactory === 'function') {
this._aiCoreOrKey = iKeyOrClientFactory();
} else {
this._aiCoreOrKey = iKeyOrClientFactory;
}
this._asyncAiCore = null;
}
private _withAIClient(callback: (aiCore: IAppInsightsCore) => void): void {
if (!this._aiCoreOrKey) {
return;
}
if (typeof this._aiCoreOrKey !== 'string') {
callback(this._aiCoreOrKey);
return;
}
if (!this._asyncAiCore) {
this._asyncAiCore = getClient(this._aiCoreOrKey, this._isInternalTelemetry, this._xhrOverride);
}
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);
const name = this._eventPrefix + '/' + eventName;
try {
this._withAIClient((aiClient) => {
aiClient.pluginVersionString = data?.properties.version ?? 'Unknown';
aiClient.track({
name,
baseData: { name, properties: data?.properties, measurements: data?.measurements }
});
});
} catch { }
}
flush(): Promise<any> {
if (this._aiCoreOrKey) {
return new Promise(resolve => {
this._withAIClient((aiClient) => {
aiClient.unload(true, () => {
this._aiCoreOrKey = undefined;
resolve(undefined);
});
});
});
}
return Promise.resolve(undefined);
}
}

View File

@@ -45,6 +45,10 @@ export class ServerTelemetryChannel extends Disposable implements IServerChannel
return Promise.resolve();
}
case 'ping': {
return;
}
}
// Command we cannot handle so we throw an error
throw new Error(`IPC Command ${command} not found`);

View File

@@ -6,7 +6,7 @@
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProductService } from 'vs/platform/product/common/productService';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ClassifiedEvent, IGDPRProperty, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryData, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { NullTelemetryServiceShape } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -37,7 +37,7 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
return super.publicLog(eventName, data, anonymizeFilePaths);
}
override publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
override publicLog2<E extends ClassifiedEvent<T> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>, anonymizeFilePaths?: boolean): Promise<void> {
return this.publicLog(eventName, data as ITelemetryData | undefined, anonymizeFilePaths);
}
@@ -48,7 +48,7 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
return super.publicLogError(errorEventName, data);
}
override publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<void> {
override publicLogError2<E extends ClassifiedEvent<T> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>): Promise<void> {
return this.publicLogError(eventName, data as ITelemetryData | undefined);
}

View File

@@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IProductService } from 'vs/platform/product/common/productService';
import { verifyMicrosoftInternalDomain } from 'vs/platform/telemetry/common/commonProperties';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ICustomEndpointTelemetryService, ITelemetryData, ITelemetryEndpoint, ITelemetryInfo, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry';
@@ -250,6 +251,18 @@ function flatKeys(result: string[], prefix: string, value: { [key: string]: any
}
}
/**
* Whether or not this is an internal user
* @param productService The product service
* @param configService The config servivce
* @returns true if internal, false otherwise
*/
export function isInternalTelemetry(productService: IProductService, configService: IConfigurationService) {
const msftInternalDomains = productService.msftInternalDomains || [];
const internalTesting = configService.getValue<boolean>('telemetry.internalTesting');
return verifyMicrosoftInternalDomain(msftInternalDomains) || internalTesting;
}
interface IPathEnvironment {
appRoot: string;
extensionsPath: string;

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js';
import * as https from 'https';
import { AbstractOneDataSystemAppender, IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender';
export class OneDataSystemAppender extends AbstractOneDataSystemAppender {
constructor(
isInternalTelemetry: boolean,
eventPrefix: string,
defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing
) {
// Override the way events get sent since node doesn't have XHTMLRequest
const customHttpXHROverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete) => {
const options = {
method: 'POST',
headers: {
...payload.headers,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload.data)
}
};
try {
const req = https.request(payload.urlString, options, res => {
res.on('data', function (responseData) {
oncomplete(res.statusCode ?? 200, res.headers as Record<string, any>, responseData.toString());
});
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
res.on('error', function (err) {
oncomplete(0, {});
});
});
req.write(payload.data);
req.end();
} catch {
// If it errors out, send status of 0 and a blank response to oncomplete so we can retry events
oncomplete(0, {});
}
}
};
super(isInternalTelemetry, eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride);
}
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { TelemetryClient } from 'applicationinsights';
import { onUnexpectedError } from 'vs/base/common/errors';
import { mixin } from 'vs/base/common/objects';
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
async function getClient(aiKey: string): Promise<TelemetryClient> {
const appInsights = await import('applicationinsights');
let client: TelemetryClient;
if (appInsights.defaultClient) {
client = new appInsights.TelemetryClient(aiKey);
client.channel.setUseDiskRetryCaching(true);
} else {
appInsights.setup(aiKey)
.setAutoCollectRequests(false)
.setAutoCollectPerformance(false)
.setAutoCollectExceptions(false)
.setAutoCollectDependencies(false)
.setAutoDependencyCorrelation(false)
.setAutoCollectConsole(false)
.setInternalLogging(false, false)
.setUseDiskRetryCaching(true)
.start();
client = appInsights.defaultClient;
}
if (aiKey.indexOf('AIF-') === 0) {
client.config.endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1';
}
return client;
}
export class AppInsightsAppender implements ITelemetryAppender {
private _aiClient: string | TelemetryClient | undefined;
private _asyncAIClient: Promise<TelemetryClient> | null;
constructor(
private _eventPrefix: string,
private _defaultData: { [key: string]: any } | null,
aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing
) {
if (!this._defaultData) {
this._defaultData = Object.create(null);
}
if (typeof aiKeyOrClientFactory === 'function') {
this._aiClient = aiKeyOrClientFactory();
} else {
this._aiClient = aiKeyOrClientFactory;
}
this._asyncAIClient = null;
}
private _withAIClient(callback: (aiClient: TelemetryClient) => void): void {
if (!this._aiClient) {
return;
}
if (typeof this._aiClient !== 'string') {
callback(this._aiClient);
return;
}
if (!this._asyncAIClient) {
this._asyncAIClient = getClient(this._aiClient);
}
this._asyncAIClient.then(
(aiClient) => {
callback(aiClient);
},
(err) => {
onUnexpectedError(err);
console.error(err);
}
);
}
log(eventName: string, data?: any): void {
if (!this._aiClient) {
return;
}
data = mixin(data, this._defaultData);
data = validateTelemetryData(data);
// Attemps to suppress https://github.com/microsoft/vscode/issues/140624
try {
this._withAIClient((aiClient) => aiClient.trackEvent({
name: this._eventPrefix + '/' + eventName,
properties: data.properties,
measurements: data.measurements
}));
} catch { }
}
flush(): Promise<any> {
if (this._aiClient) {
return new Promise(resolve => {
this._withAIClient((aiClient) => {
// Attempts to suppress https://github.com/microsoft/vscode/issues/140624
try {
aiClient.flush({
callback: () => {
// all data flushed
this._aiClient = undefined;
resolve(undefined);
}
});
} catch { }
});
});
}
return Promise.resolve(undefined);
}
}

View File

@@ -2,39 +2,35 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Contracts, TelemetryClient } from 'applicationinsights';
import { ITelemetryItem, ITelemetryUnloadState } from '@microsoft/1ds-core-js';
import * as assert from 'assert';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
import { IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender';
class AppInsightsMock extends TelemetryClient {
public override config: any;
public override channel: any;
public events: Contracts.EventTelemetry[] = [];
class AppInsightsCoreMock implements IAppInsightsCore {
pluginVersionString: string = 'Test Runner';
public events: any[] = [];
public IsTrackingPageView: boolean = false;
public exceptions: any[] = [];
constructor() {
super('testKey');
public track(event: ITelemetryItem) {
this.events.push(event.baseData);
}
public override trackEvent(event: any) {
this.events.push(event);
}
public override flush(options: any): void {
// called on dispose
public unload(isAsync: boolean, unloadComplete: (unloadState: ITelemetryUnloadState) => void): void {
// No-op
}
}
suite('AIAdapter', () => {
let appInsightsMock: AppInsightsMock;
let adapter: AppInsightsAppender;
let prefix = 'prefix';
let appInsightsMock: AppInsightsCoreMock;
let adapter: OneDataSystemWebAppender;
const prefix = 'prefix';
setup(() => {
appInsightsMock = new AppInsightsMock();
adapter = new AppInsightsAppender(prefix, undefined!, () => appInsightsMock);
appInsightsMock = new AppInsightsCoreMock();
adapter = new OneDataSystemWebAppender(false, prefix, undefined!, () => appInsightsMock);
});
teardown(() => {
@@ -49,11 +45,11 @@ suite('AIAdapter', () => {
});
test('addional data', () => {
adapter = new AppInsightsAppender(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
adapter = new OneDataSystemWebAppender(false, prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
adapter.log('testEvent');
assert.strictEqual(appInsightsMock.events.length, 1);
let [first] = appInsightsMock.events;
const [first] = appInsightsMock.events;
assert.strictEqual(first.name, `${prefix}/testEvent`);
assert.strictEqual(first.properties!['first'], '1st');
assert.strictEqual(first.measurements!['second'], 2);
@@ -73,21 +69,21 @@ suite('AIAdapter', () => {
}
assert(reallyLongPropertyValue.length > 8192);
let data = Object.create(null);
const data = Object.create(null);
data[reallyLongPropertyName] = '1234';
data['reallyLongPropertyValue'] = reallyLongPropertyValue;
adapter.log('testEvent', data);
assert.strictEqual(appInsightsMock.events.length, 1);
for (let prop in appInsightsMock.events[0].properties!) {
for (const prop in appInsightsMock.events[0].properties!) {
assert(prop.length < 150);
assert(appInsightsMock.events[0].properties![prop].length < 8192);
}
});
test('Different data types', () => {
let date = new Date();
const date = new Date();
adapter.log('testEvent', { favoriteDate: date, likeRed: false, likeBlue: true, favoriteNumber: 1, favoriteColor: 'blue', favoriteCars: ['bmw', 'audi', 'ford'] });
assert.strictEqual(appInsightsMock.events.length, 1);

View File

@@ -49,8 +49,7 @@ import { RequestService } from 'vs/platform/request/node/requestService';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { getPiiPathsFromEnvironment, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { getPiiPathsFromEnvironment, isInternalTelemetry, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry';
import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService';
@@ -70,8 +69,9 @@ import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/co
import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService';
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
const eventPrefix = 'monacoworkbench';
const eventPrefix = 'adsworkbench'; // {{SQL CARBON EDIT}} Use our own event prefix
export async function setupServerServices(connectionToken: ServerConnectionToken, args: ServerParsedArgs, REMOTE_DATA_FOLDER: string, disposables: DisposableStore) {
const services = new ServiceCollection();
@@ -119,16 +119,17 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
let appInsightsAppender: ITelemetryAppender = NullAppender;
let oneDsAppender: ITelemetryAppender = NullAppender;
const machineId = await getMachineId();
const isInternal = isInternalTelemetry(productService, configurationService);
if (supportsTelemetry(productService, environmentService)) {
if (productService.aiConfig && productService.aiConfig.asimovKey) {
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, productService.aiConfig.asimovKey);
disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
if (productService.aiConfig && productService.aiConfig.ariaKey) {
oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
}
const config: ITelemetryServiceConfig = {
appenders: [appInsightsAppender],
appenders: [oneDsAppender],
commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, productService.msftInternalDomains, environmentService.installSourcePath, 'remoteAgent'),
piiPaths: getPiiPathsFromEnvironment(environmentService)
};
@@ -183,7 +184,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, extensionManagementCLIService, logService, extensionHostStatusService, extensionsScannerService);
socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel);
const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), appInsightsAppender);
const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender);
socketServer.registerChannel('telemetry', telemetryChannel);
socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService));

View File

@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
const appender = new OneDataSystemAppender(false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
process.once('exit', () => appender.flush());
const channel = new TelemetryAppenderChannel([appender]);
const server = new Server('telemetry');
server.registerChannel('telemetryAppender', channel);

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservableValue } from 'vs/base/common/observableValue';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -11,103 +10,21 @@ 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 { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
import { ITelemetryServiceConfig, TelemetryService as BaseTelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { ITelemetryAppender, NullTelemetryService, supportsTelemetry, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
import { getTelemetryLevel, isInternalTelemetry, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties';
class WebAppInsightsAppender implements ITelemetryAppender {
private _aiClient: ApplicationInsights | undefined;
private _aiClientLoaded = false;
private _telemetryCache: { eventName: string; data: any }[] = [];
constructor(private _eventPrefix: string, aiKey: string) {
const endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1';
import('@microsoft/applicationinsights-web').then(aiLibrary => {
this._aiClient = new aiLibrary.ApplicationInsights({
config: {
instrumentationKey: aiKey,
endpointUrl,
disableAjaxTracking: true,
disableExceptionTracking: true,
disableFetchTracking: true,
disableCorrelationHeaders: true,
disableCookiesUsage: true,
autoTrackPageVisitTime: false,
emitLineDelimitedJson: true,
},
});
this._aiClient.loadAppInsights();
// Client is loaded we can now flush the cached events
this._aiClientLoaded = true;
this._telemetryCache.forEach(cacheEntry => this.log(cacheEntry.eventName, cacheEntry.data));
this._telemetryCache = [];
// If we cannot access the endpoint this most likely means it's being blocked
// and we should not attempt to send any telemetry.
fetch(endpointUrl).catch(() => (this._aiClient = undefined));
}).catch(err => {
console.error(err);
});
}
/**
* Logs a telemetry event with eventName and data
* @param eventName The event name
* @param data The data associated with the events
*/
public log(eventName: string, data: any): void {
if (!this._aiClient && this._aiClientLoaded) {
return;
} else if (!this._aiClient && !this._aiClientLoaded) {
this._telemetryCache.push({ eventName, data });
return;
}
data = validateTelemetryData(data);
// Web does not expect properties and measurements so we must
// spread them out. This is different from desktop which expects them
data = { ...data.properties, ...data.measurements };
// undefined assertion is ok since above two if statements cover both cases
this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data);
}
/**
* Flushes all the telemetry data still in the buffer
*/
public flush(): Promise<any> {
if (this._aiClient) {
this._aiClient.flush();
this._aiClient = undefined;
}
return Promise.resolve(undefined);
}
}
class WebTelemetryAppender implements ITelemetryAppender {
constructor(private _appender: ITelemetryAppender) { }
log(eventName: string, data: any): void {
this._appender.log(eventName, data);
}
flush(): Promise<void> {
return this._appender.flush();
}
}
export class TelemetryService extends Disposable implements ITelemetryService {
declare readonly _serviceBrand: undefined;
private impl: ITelemetryService;
private impl: ITelemetryService = NullTelemetryService;
public readonly sendErrorTelemetry = true;
constructor(
@@ -120,19 +37,46 @@ export class TelemetryService extends Disposable implements ITelemetryService {
) {
super();
if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey) {
this.impl = this.initializeService(environmentService, loggerService, configurationService, storageService, productService, remoteAgentService);
// When the level changes it could change from off to on and we want to make sure telemetry is properly intialized
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TELEMETRY_SETTING_ID)) {
this.impl = this.initializeService(environmentService, loggerService, configurationService, storageService, productService, remoteAgentService);
}
}));
}
/**
* Initializes the telemetry service to be a full fledged service.
* This is only done once and only when telemetry is enabled as this will also ping the endpoint to
* ensure its not adblocked and we can send telemetry
*/
private initializeService(
environmentService: IBrowserWorkbenchEnvironmentService,
loggerService: ILoggerService,
configurationService: IConfigurationService,
storageService: IStorageService,
productService: IProductService,
remoteAgentService: IRemoteAgentService
) {
const telemetrySupported = supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey;
if (telemetrySupported && getTelemetryLevel(configurationService) !== TelemetryLevel.NONE && this.impl === NullTelemetryService) {
// 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 appenders = [];
const isInternal = isInternalTelemetry(productService, configurationService);
const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(isInternal, 'adsworkbench', null, productService.aiConfig?.ariaKey); // {{SQL CARBON EDIT}} Use our own event prefix
appenders.push(telemetryProvider);
appenders.push(new TelemetryLogAppender(loggerService, environmentService));
const config: ITelemetryServiceConfig = {
appenders: [new WebTelemetryAppender(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,
};
this.impl = this._register(new BaseTelemetryService(config, configurationService, productService));
} else {
this.impl = NullTelemetryService;
return this._register(new BaseTelemetryService(config, configurationService, productService));
}
return this.impl;
}
setExperimentProperty(name: string, value: string): void {
@@ -164,4 +108,5 @@ export class TelemetryService extends Disposable implements ITelemetryService {
}
}
registerSingleton(ITelemetryService, TelemetryService);
registerSingleton(ITelemetryService, TelemetryService, true);