/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import * as appInsights from 'applicationinsights'; import {isObject} from 'vs/base/common/types'; import {safeStringify, mixin} from 'vs/base/common/objects'; import {TPromise} from 'vs/base/common/winjs.base'; import {ITelemetryAppender} from '../common/telemetry'; let _initialized = false; function ensureAIEngineIsInitialized(): void { if (_initialized === false) { // we need to pass some fake key, otherwise AI throws an exception appInsights.setup('2588e01f-f6c9-4cd6-a348-143741f8d702') .setAutoCollectConsole(false) .setAutoCollectExceptions(false) .setAutoCollectPerformance(false) .setAutoCollectRequests(false); _initialized = true; } } function getClient(aiKey: string): typeof appInsights.client { ensureAIEngineIsInitialized(); const client = appInsights.getClient(aiKey); client.channel.setOfflineMode(true); client.context.tags[client.context.keys.deviceMachineName] = ''; //prevent App Insights from reporting machine name if (aiKey.indexOf('AIF-') === 0) { client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1'; } return client; } interface Properties { [key: string]: string; } interface Measurements { [key: string]: number; } export class AppInsightsAppender implements ITelemetryAppender { private _aiClient: typeof appInsights.client; constructor( private _eventPrefix: string, private _defaultData: { [key: string]: any }, aiKeyOrClientFactory: string | (() => typeof appInsights.client) // allow factory function for testing ) { if (!this._defaultData) { this._defaultData = Object.create(null); } if (typeof aiKeyOrClientFactory === 'string') { this._aiClient = getClient(aiKeyOrClientFactory); } else if (typeof aiKeyOrClientFactory === 'function') { this._aiClient = aiKeyOrClientFactory(); } } private static _getData(data?: any): { properties: Properties, measurements: Measurements } { const properties: Properties = Object.create(null); const measurements: Measurements = Object.create(null); const flat = Object.create(null); AppInsightsAppender._flaten(data, flat); for (let prop in flat) { // enforce property names less than 150 char, take the last 150 char prop = prop.length > 150 ? prop.substr(prop.length - 149) : prop; var value = flat[prop]; if (typeof value === 'number') { measurements[prop] = value; } else if (typeof value === 'boolean') { measurements[prop] = value ? 1 : 0; } else if (typeof value === 'string') { //enforce property value to be less than 1024 char, take the first 1024 char properties[prop] = value.substring(0, 1023); } else if (typeof value !== 'undefined' && value !== null) { properties[prop] = value; } } return { properties, measurements }; } private static _flaten(obj: any, result: {[key: string]: any }, order: number = 0, prefix?: string): void { if (!obj) { return; } for(var item of Object.getOwnPropertyNames(obj)){ const value = obj[item]; const index = prefix ? prefix + item : item; if (Array.isArray(value)) { result[index] = safeStringify(value); } else if (value instanceof Date) { // TODO unsure why this is here and not in _getData result[index] = value.toISOString(); } else if (isObject(value)) { if (order < 2) { AppInsightsAppender._flaten(value, result, order + 1, index + '.'); } else { result[index] = safeStringify(value); } } else { result[index] = value; } } } public log(eventName: string, data?: any): void { if (!this._aiClient) { return; } data = mixin(data, this._defaultData); let {properties, measurements} = AppInsightsAppender._getData(data); this._aiClient.trackEvent(this._eventPrefix + '/' + eventName, properties, measurements); } public dispose(): TPromise { if (this._aiClient) { return new TPromise(resolve => { this._aiClient.sendPendingData(() => { // all data flushed this._aiClient = undefined; resolve(void 0); }); }); } } }