提交 ae96e2cb 编写于 作者: S Sofian Hnaide

refactor AIAdatper out of ai telemetry appender

上级 60b63e46
/*---------------------------------------------------------------------------------------------
* 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 types = require('vs/base/common/types');
import {safeStringify} from 'vs/base/common/objects';
import appInsights = require('applicationinsights');
export interface IAIAdapter {
log(eventName: string, data?: any): void;
logException(exception: any): void;
dispose(): void;
}
export class AIAdapter implements IAIAdapter {
private appInsights: typeof appInsights.client;
constructor(
private aiKey: string,
private eventPrefix: string,
/* for test only */
client?: any
) {
// for test
if (client) {
this.appInsights = client;
return;
}
if (aiKey) {
// if another client is already initialized
if (appInsights.client) {
this.appInsights = appInsights.getClient(aiKey);
// no other way to enable offline mode
this.appInsights.channel.setOfflineMode(true);
} else {
this.appInsights = appInsights.setup(aiKey)
.setAutoCollectRequests(false)
.setAutoCollectPerformance(false)
.setAutoCollectExceptions(false)
.setOfflineMode(true)
.start()
.client;
}
if(aiKey.indexOf('AIF-') === 0) {
this.appInsights.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
}
this.setupAIClient(this.appInsights);
}
}
private setupAIClient(client: typeof appInsights.client): void {
//prevent App Insights from reporting machine name
if (client && client.context &&
client.context.keys && client.context.tags) {
var machineNameKey = client.context.keys.deviceMachineName;
client.context.tags[machineNameKey] = '';
}
}
private getData(data?: any): any {
var properties: {[key: string]: string;} = {};
var measurements: {[key: string]: number;} = {};
var event_data = this.flaten(data);
for(var prop in event_data) {
// enforce property names less than 150 char, take the last 150 char
var propName = prop && prop.length > 150 ? prop.substr( prop.length - 149) : prop;
var property = event_data[prop];
if (types.isNumber(property)) {
measurements[propName] = property;
} else if (types.isBoolean(property)) {
measurements[propName] = property ? 1:0;
} else if (types.isString(property)) {
//enforce proeprty value to be less than 1024 char, take the first 1024 char
var propValue = property && property.length > 1024 ? property.substring(0, 1023): property;
properties[propName] = propValue;
} else if (!types.isUndefined(property) && property !== null) {
properties[propName] = property;
}
}
return {
properties: properties,
measurements: measurements
};
}
private flaten(obj:any, order:number = 0, prefix? : string): any {
var result:{[key:string]: any} = {};
var properties = obj ? Object.getOwnPropertyNames(obj) : [];
for (var i =0; i < properties.length; i++) {
var item = properties[i];
var index = prefix ? prefix + item : item;
if (types.isArray(obj[item])) {
try {
result[index] = safeStringify(obj[item]);
} catch (e) {
// workaround for catching the edge case for #18383
// safe stringfy should never throw circular object exception
result[index] = '[Circular-Array]';
}
} else if (obj[item] instanceof Date) {
result[index] = (<Date> obj[item]).toISOString();
} else if (types.isObject(obj[item])) {
if (order < 2) {
var item_result = this.flaten(obj[item], order + 1, index + '.');
for (var prop in item_result) {
result[prop] = item_result[prop];
}
} else {
try {
result[index] = safeStringify(obj[item]);
} catch (e) {
// workaround for catching the edge case for #18383
// safe stringfy should never throw circular object exception
result[index] = '[Circular]';
}
}
} else {
result[index] = obj[item];
}
}
return result;
}
public log(eventName: string, data?: any): void {
var result = this.getData(data);
if (this.appInsights) {
this.appInsights.trackEvent(this.eventPrefix+'/'+eventName, result.properties, result.measurements);
}
}
public logException(exception: any): void {
if (this.appInsights) {
this.appInsights.trackException(exception);
}
}
public dispose(): void {
this.appInsights = null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 assert from 'assert';
import { AIAdapter } from 'vs/base/node/aiAdapter';
interface IAppInsightsEvent {
eventName: string;
properties?: {string?: string;};
measurements?: {string?: number;}
}
class AppInsightsMock {
public events: IAppInsightsEvent[]=[];
public IsTrackingPageView: boolean = false;
public exceptions: any[] =[];
public trackEvent(eventName: string, properties?: {string?: string;}, measurements?: {string?: number;}): void {
this.events.push({
eventName: eventName,
properties: properties,
measurements: measurements
});
}
public trackPageView(): void {
this.IsTrackingPageView = true;
}
public trackException(exception: any): void {
this.exceptions.push(exception);
}
}
suite('AIAdapter', () => {
var appInsightsMock: AppInsightsMock;
var adapter: AIAdapter;
var prefix = 'prefix';
setup(() => {
appInsightsMock = new AppInsightsMock();
adapter = new AIAdapter(null, prefix, appInsightsMock);
});
teardown(() => {
adapter.dispose();
});
test('Simple event', () => {
adapter.log('testEvent');
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, `${prefix}/testEvent`);
});
test('Track UnhandledError as exception and events', () => {
var sampleError = new Error('test');
adapter.logException(sampleError);
assert.equal(appInsightsMock.exceptions.length, 1);
});
test('property limits', () => {
var reallyLongPropertyName = 'abcdefghijklmnopqrstuvwxyz';
for (var i =0; i <6; i++) {
reallyLongPropertyName +='abcdefghijklmnopqrstuvwxyz';
}
assert(reallyLongPropertyName.length > 150);
var reallyLongPropertyValue = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123';
for (var i =0; i <21; i++) {
reallyLongPropertyValue +='abcdefghijklmnopqrstuvwxyz012345678901234567890123';
}
assert(reallyLongPropertyValue.length > 1024);
var data = {};
data[reallyLongPropertyName] = '1234';
data['reallyLongPropertyValue'] = reallyLongPropertyValue;
adapter.log('testEvent', data);
assert.equal(appInsightsMock.events.length, 1);
for (var prop in appInsightsMock.events[0].properties){
assert(prop.length < 150);
assert(appInsightsMock.events[0].properties[prop].length <1024);
}
});
test('Different data types', () => {
var date = new Date();
adapter.log('testEvent', { favoriteDate: date, likeRed: false, likeBlue: true, favoriteNumber:1, favoriteColor: 'blue', favoriteCars: ['bmw', 'audi', 'ford']});
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, `${prefix}/testEvent`);
assert.equal(appInsightsMock.events[0].properties['favoriteColor'], 'blue');
assert.equal(appInsightsMock.events[0].measurements['likeRed'], 0);
assert.equal(appInsightsMock.events[0].measurements['likeBlue'], 1);
assert.equal(appInsightsMock.events[0].properties['favoriteDate'], date.toISOString());
assert.equal(appInsightsMock.events[0].properties['favoriteCars'], JSON.stringify(['bmw', 'audi', 'ford']));
assert.equal(appInsightsMock.events[0].measurements['favoriteNumber'], 1);
});
test('Nested data', () => {
adapter.log('testEvent', {
window : {
title: 'some title',
measurements: {
width: 100,
height: 200
}
},
nestedObj: {
nestedObj2: {
nestedObj3: {
testProperty: 'test',
}
},
testMeasurement:1
}
});
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, `${prefix}/testEvent`);
assert.equal(appInsightsMock.events[0].properties['window.title'], 'some title');
assert.equal(appInsightsMock.events[0].measurements['window.measurements.width'], 100);
assert.equal(appInsightsMock.events[0].measurements['window.measurements.height'], 200);
assert.equal(appInsightsMock.events[0].properties['nestedObj.nestedObj2.nestedObj3'], JSON.stringify({"testProperty":"test"}));
assert.equal(appInsightsMock.events[0].measurements['nestedObj.testMeasurement'],1);
});
});
\ No newline at end of file
......@@ -7,15 +7,13 @@
/* tslint:disable:semicolon */
import errors = require('vs/base/common/errors');
import types = require('vs/base/common/types');
import {safeStringify} from 'vs/base/common/objects';
import {IStorageService} from 'vs/platform/storage/common/storage';
import {ITelemetryAppender} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {AIAdapter, IAIAdapter} from 'vs/base/node/aiAdapter';
import winreg = require('winreg');
import os = require('os');
import appInsights = require('applicationinsights');
class StorageKeys {
public static sqmUserId: string = 'telemetry.sqm.userId';
......@@ -26,15 +24,15 @@ class StorageKeys {
export class NodeAppInsightsTelemetryAppender implements ITelemetryAppender {
public static EVENT_NAME_PREFIX: string = 'monacoworkbench/';
public static EVENT_NAME_PREFIX: string = 'monacoworkbench';
private static SQM_KEY: string = '\\Software\\Microsoft\\SQMClient';
private storageService:IStorageService;
private contextService: IWorkspaceContextService;
private appInsights: typeof appInsights.client;
private appInsightsVortex: typeof appInsights.client;
private appInsights: IAIAdapter;
private appInsightsVortex: IAIAdapter;
protected commonProperties: {[key:string] : string};
protected commonMetrics: {[key: string]: number};
......@@ -66,35 +64,16 @@ export class NodeAppInsightsTelemetryAppender implements ITelemetryAppender {
}
if (key) {
this.appInsights = appInsights.setup(key)
.setAutoCollectRequests(false)
.setAutoCollectPerformance(false)
.setAutoCollectExceptions(false)
.setOfflineMode(true)
.start()
.client;
this.setupAIClient(this.appInsights);
this.appInsights = new AIAdapter(key, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX);
}
if(asimovKey) {
this.appInsightsVortex = appInsights.getClient(asimovKey);
this.appInsightsVortex.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
this.setupAIClient(this.appInsightsVortex);
this.appInsightsVortex = new AIAdapter(asimovKey, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX);
}
this.loadAddtionaProperties();
}
private setupAIClient(client: typeof appInsights.client): void {
//prevent App Insights from reporting machine name
if (client && client.context &&
client.context.keys && client.context.tags) {
var machineNameKey = client.context.keys.deviceMachineName;
client.context.tags[machineNameKey] = '';
}
}
private loadAddtionaProperties(): void {
// add shell & render version
if (process.versions) {
......@@ -177,109 +156,48 @@ export class NodeAppInsightsTelemetryAppender implements ITelemetryAppender {
}
}
private getData(data?: any): any {
var properties: {[key: string]: string;} = {};
var measurements: {[key: string]: number;} = {};
var event_data = this.flaten(data);
for(var prop in event_data) {
// enforce property names less than 150 char, take the last 150 char
var propName = prop && prop.length > 150 ? prop.substr( prop.length - 149) : prop;
var property = event_data[prop];
if (types.isNumber(property)) {
measurements[propName] = property;
} else if (types.isBoolean(property)) {
measurements[propName] = property ? 1:0;
} else if (types.isString(property)) {
//enforce proeprty value to be less than 1024 char, take the first 1024 char
var propValue = property && property.length > 1024 ? property.substring(0, 1023): property;
properties[propName] = propValue;
} else if (!types.isUndefined(property) && property !== null) {
properties[propName] = property;
}
}
properties = this.addCommonProperties(properties);
measurements = this.addCommonMetrics(measurements);
return {
properties: properties,
measurements: measurements
};
}
private flaten(obj:any, order:number = 0, prefix? : string): any {
var result:{[key:string]: any} = {};
var properties = obj ? Object.getOwnPropertyNames(obj) : [];
for (var i =0; i < properties.length; i++) {
var item = properties[i];
var index = prefix ? prefix + item : item;
if (types.isArray(obj[item])) {
try {
result[index] = safeStringify(obj[item]);
} catch (e) {
// workaround for catching the edge case for #18383
// safe stringfy should never throw circular object exception
result[index] = '[Circular-Array]';
}
} else if (obj[item] instanceof Date) {
result[index] = (<Date> obj[item]).toISOString();
} else if (types.isObject(obj[item])) {
if (order < 2) {
var item_result = this.flaten(obj[item], order + 1, index + '.');
for (var prop in item_result) {
result[prop] = item_result[prop];
}
} else {
try {
result[index] = safeStringify(obj[item]);
} catch (e) {
// workaround for catching the edge case for #18383
// safe stringfy should never throw circular object exception
result[index] = '[Circular]';
}
}
} else {
result[index] = obj[item];
}
}
return result;
}
public log(eventName: string, data?: any): void {
var result = this.getData(data);
data = data || Object.create(null);
data = this.addCommonMetrics(data);
data = this.addCommonProperties(data);
if (this.appInsights) {
if (eventName === 'UnhandledError' && data) {
this.appInsights.trackException(data);
this.appInsights.logException(data);
}
this.appInsights.trackEvent(NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+eventName, result.properties, result.measurements);
this.appInsights.log(eventName, data);
}
if (this.appInsightsVortex) {
if (eventName === 'UnhandledError' && data) {
this.appInsightsVortex.trackException(data);
this.appInsightsVortex.logException(data);
}
this.appInsightsVortex.trackEvent(NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+eventName, result.properties, result.measurements);
this.appInsightsVortex.log(eventName, data);
}
}
public dispose(): void {
if (this.appInsights) {
this.appInsights.dispose();
}
if (this.appInsightsVortex) {
this.appInsightsVortex.dispose();
}
this.appInsights = null;
this.appInsightsVortex = null;
}
protected addCommonProperties(properties: { [key: string]: string }): any {
protected addCommonProperties(properties: any): any {
for (var prop in this.commonProperties) {
properties['common.' + prop] = this.commonProperties[prop];
}
return properties;
}
protected addCommonMetrics = function (metrics: { [key: string]: number }): any {
protected addCommonMetrics(metrics: any): any {
for (var prop in this.commonMetrics) {
metrics['common.' + prop] = this.commonMetrics[prop];
}
......
......@@ -5,34 +5,36 @@
'use strict';
import * as assert from 'assert';
import {IAIAdapter} from 'vs/base/node/aiAdapter';
import { NodeAppInsightsTelemetryAppender } from 'vs/workbench/parts/telemetry/node/nodeAppInsightsTelemetryAppender';
interface IAppInsightsEvent {
eventName: string;
properties?: {string?: string;};
measurements?: {string?: number;}
data: any;
}
class AppInsightsMock {
class AIAdapterMock implements IAIAdapter {
public events: IAppInsightsEvent[]=[];
public IsTrackingPageView: boolean = false;
public exceptions: any[] =[];
public trackEvent(eventName: string, properties?: {string?: string;}, measurements?: {string?: number;}): void {
constructor(private prefix: string, private eventPrefix: string, client?: any) {
}
public log(eventName: string, data?: any): void {
this.events.push({
eventName: eventName,
properties: properties,
measurements: measurements
eventName: this.prefix+'/'+eventName,
data: data
});
}
public trackPageView(): void {
this.IsTrackingPageView = true;
}
public trackException(exception: any): void {
public logException(exception: any): void {
this.exceptions.push(exception);
}
public dispose(): void {
}
}
class ContextServiceMock {
......@@ -52,11 +54,11 @@ class ContextServiceMock {
}
suite('Telemetry - AppInsightsTelemetryAppender', () => {
var appInsightsMock: AppInsightsMock;
var appInsightsMock: AIAdapterMock;
var appender: NodeAppInsightsTelemetryAppender;
setup(() => {
appInsightsMock = new AppInsightsMock();
appInsightsMock = new AIAdapterMock(NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX);
appender = new NodeAppInsightsTelemetryAppender(null,<any> new ContextServiceMock('123'), appInsightsMock);
});
......@@ -68,7 +70,7 @@ suite('Telemetry - AppInsightsTelemetryAppender', () => {
appender.log('testEvent');
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'testEvent');
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'/testEvent');
});
test('Track UnhandledError as exception and events', () => {
......@@ -77,79 +79,25 @@ suite('Telemetry - AppInsightsTelemetryAppender', () => {
appender.log('UnhandledError', sampleError);
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'UnhandledError');
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'/UnhandledError');
assert.equal(appInsightsMock.exceptions.length, 1);
});
test('property limits', () => {
var reallyLongPropertyName = 'abcdefghijklmnopqrstuvwxyz';
for (var i =0; i <6; i++) {
reallyLongPropertyName +='abcdefghijklmnopqrstuvwxyz';
}
assert(reallyLongPropertyName.length > 150);
var reallyLongPropertyValue = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123';
for (var i =0; i <21; i++) {
reallyLongPropertyValue +='abcdefghijklmnopqrstuvwxyz012345678901234567890123';
}
assert(reallyLongPropertyValue.length > 1024);
var data = {};
data[reallyLongPropertyName] = '1234';
data['reallyLongPropertyValue'] = reallyLongPropertyValue;
appender.log('testEvent', data);
assert.equal(appInsightsMock.events.length, 1);
for (var prop in appInsightsMock.events[0].properties){
assert(prop.length < 150);
assert(appInsightsMock.events[0].properties[prop].length <1024);
}
});
test('Different data types', () => {
var date = new Date();
appender.log('testEvent', { favoriteDate: date, likeRed: false, likeBlue: true, favoriteNumber:1, favoriteColor: 'blue', favoriteCars: ['bmw', 'audi', 'ford']});
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'testEvent');
assert.equal(appInsightsMock.events[0].properties['favoriteColor'], 'blue');
assert.equal(appInsightsMock.events[0].measurements['likeRed'], 0);
assert.equal(appInsightsMock.events[0].measurements['likeBlue'], 1);
assert.equal(appInsightsMock.events[0].properties['favoriteDate'], date.toISOString());
assert.equal(appInsightsMock.events[0].properties['favoriteCars'], JSON.stringify(['bmw', 'audi', 'ford']));
assert.equal(appInsightsMock.events[0].measurements['favoriteNumber'], 1);
});
test('Nested data', () => {
test('Event with data', () => {
appender.log('testEvent', {
window : {
title: 'some title',
measurements: {
width: 100,
height: 200
}
},
nestedObj: {
nestedObj2: {
nestedObj3: {
testProperty: 'test',
}
},
testMeasurement:1
}
title: 'some title',
width: 100,
height: 200
});
assert.equal(appInsightsMock.events.length, 1);
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'testEvent');
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'/testEvent');
assert.equal(appInsightsMock.events[0].properties['window.title'], 'some title');
assert.equal(appInsightsMock.events[0].measurements['window.measurements.width'], 100);
assert.equal(appInsightsMock.events[0].measurements['window.measurements.height'], 200);
assert.equal(appInsightsMock.events[0].data['title'], 'some title');
assert.equal(appInsightsMock.events[0].data['width'], 100);
assert.equal(appInsightsMock.events[0].data['height'], 200);
assert.equal(appInsightsMock.events[0].properties['nestedObj.nestedObj2.nestedObj3'], JSON.stringify({"testProperty":"test"}));
assert.equal(appInsightsMock.events[0].measurements['nestedObj.testMeasurement'],1);
});
test('Test asimov', () => {
......@@ -158,9 +106,9 @@ suite('Telemetry - AppInsightsTelemetryAppender', () => {
appender.log('testEvent');
assert.equal(appInsightsMock.events.length, 2);
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'testEvent');
assert.equal(appInsightsMock.events[0].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'/testEvent');
// test vortex
assert.equal(appInsightsMock.events[1].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'testEvent');
assert.equal(appInsightsMock.events[1].eventName, NodeAppInsightsTelemetryAppender.EVENT_NAME_PREFIX+'/testEvent');
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册