mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -103,7 +103,7 @@ export interface IProductConfiguration {
|
||||
readonly enableTelemetry?: boolean;
|
||||
readonly openToWelcomeMainPage?: boolean;
|
||||
readonly aiConfig?: {
|
||||
readonly asimovKey: string;
|
||||
readonly ariaKey: string;
|
||||
};
|
||||
|
||||
readonly sendASmile?: {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
24
src/vs/platform/telemetry/browser/1dsAppender.ts
Normal file
24
src/vs/platform/telemetry/browser/1dsAppender.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
148
src/vs/platform/telemetry/common/1dsAppender.ts
Normal file
148
src/vs/platform/telemetry/common/1dsAppender.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
51
src/vs/platform/telemetry/node/1dsAppender.ts
Normal file
51
src/vs/platform/telemetry/node/1dsAppender.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user