提交 77e09898 编写于 作者: A Alexandru Dima

Merge pull request #3032 from SofianHn/master

Adding optout option for telemetry
......@@ -5,28 +5,12 @@
'use strict';
import Platform = require('vs/base/common/platform');
import Objects = require('vs/base/common/objects');
import uuid = require('vs/base/common/uuid');
import * as Platform from 'vs/base/common/platform';
import * as uuid from 'vs/base/common/uuid';
import {AbstractTelemetryService} from 'vs/platform/telemetry/common/abstractTelemetryService';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {ITelemetryService, ITelemetryServiceConfig} from 'vs/platform/telemetry/common/telemetry';
import {IdleMonitor, UserStatus} from 'vs/base/browser/idleMonitor';
export interface TelemetryServiceConfig {
enableTelemetry?: boolean;
enableHardIdle?: boolean;
enableSoftIdle?: boolean;
sessionID?: string;
commitHash?: string;
version?: string;
}
const DefaultTelemetryServiceConfig: TelemetryServiceConfig = {
enableTelemetry: true,
enableHardIdle: true,
enableSoftIdle: true
};
export class MainTelemetryService extends AbstractTelemetryService implements ITelemetryService {
// how long of inactivity before a user is considered 'inactive' - 2 minutes
......@@ -34,17 +18,15 @@ export class MainTelemetryService extends AbstractTelemetryService implements IT
public static IDLE_START_EVENT_NAME = 'UserIdleStart';
public static IDLE_STOP_EVENT_NAME = 'UserIdleStop';
protected config: TelemetryServiceConfig;
private hardIdleMonitor: IdleMonitor;
private softIdleMonitor: IdleMonitor;
private eventCount: number;
private userIdHash: string;
private startTime: Date;
private optInFriendly: string[];
constructor(config?: TelemetryServiceConfig) {
this.config = Objects.withDefaults(config, DefaultTelemetryServiceConfig);
super();
constructor(config?: ITelemetryServiceConfig) {
super(config);
this.sessionId = this.config.sessionID || (uuid.generateUuid() + Date.now());
......@@ -59,6 +41,9 @@ export class MainTelemetryService extends AbstractTelemetryService implements IT
this.eventCount = 0;
this.startTime = new Date();
//holds a cache of predefined events that can be sent regardress of user optin status
this.optInFriendly = ['optInStatus'];
}
private onUserIdle(): void {
......@@ -86,11 +71,16 @@ export class MainTelemetryService extends AbstractTelemetryService implements IT
return;
}
// don't send telemetry when not enabled
// don't send telemetry when channel is not enabled
if (!this.config.enableTelemetry) {
return;
}
// don't send events when the user is optout unless the event is flaged as optin friendly
if(!this.config.userOptIn && this.optInFriendly.indexOf(eventName) === -1) {
return;
}
this.eventCount++;
data = this.addCommonProperties(data);
......
......@@ -8,12 +8,19 @@ import Errors = require('vs/base/common/errors');
import Types = require('vs/base/common/types');
import Platform = require('vs/base/common/platform');
import {TimeKeeper, IEventsListener, ITimerEvent} from 'vs/base/common/timer';
import {safeStringify} from 'vs/base/common/objects';
import {safeStringify, withDefaults} from 'vs/base/common/objects';
import {Registry} from 'vs/platform/platform';
import {ITelemetryService, ITelemetryAppender, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry';
import {ITelemetryService, ITelemetryAppender, ITelemetryInfo, ITelemetryServiceConfig} from 'vs/platform/telemetry/common/telemetry';
import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
const DefaultTelemetryServiceConfig: ITelemetryServiceConfig = {
enableTelemetry: true,
enableHardIdle: true,
enableSoftIdle: true,
userOptIn: true
};
/**
* Base class for main process telemetry services
*/
......@@ -22,7 +29,6 @@ export abstract class AbstractTelemetryService implements ITelemetryService {
public serviceId = ITelemetryService;
private toUnbind: any[];
private timeKeeper: TimeKeeper;
private appenders: ITelemetryAppender[];
private oldOnError: any;
......@@ -34,8 +40,11 @@ export abstract class AbstractTelemetryService implements ITelemetryService {
protected sessionId: string;
protected instanceId: string;
protected machineId: string;
protected toUnbind: any[];
constructor() {
protected config: ITelemetryServiceConfig;
constructor(config?: ITelemetryServiceConfig) {
this.sessionId = 'SESSION_ID_NOT_SET';
this.timeKeeper = new TimeKeeper();
this.toUnbind = [];
......@@ -49,6 +58,8 @@ export abstract class AbstractTelemetryService implements ITelemetryService {
this.enableGlobalErrorHandler();
this.errorFlushTimeout = -1;
this.config = withDefaults(config, DefaultTelemetryServiceConfig);
}
private _safeStringify(data: any): string {
......
......@@ -63,6 +63,17 @@ export interface ITelemetryAppender extends Lifecycle.IDisposable {
log(eventName: string, data?: any): void;
}
export interface ITelemetryServiceConfig {
enableTelemetry?: boolean;
userOptIn?: boolean;
enableHardIdle?: boolean;
enableSoftIdle?: boolean;
sessionID?: string;
commitHash?: string;
version?: string;
}
export function anonymize(input: string): string {
if (!input) {
return input;
......
......@@ -6,24 +6,44 @@
import getmac = require('getmac');
import crypto = require('crypto');
import {MainTelemetryService, TelemetryServiceConfig} from 'vs/platform/telemetry/browser/mainTelemetryService';
import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry';
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import * as uuid from 'vs/base/common/uuid';
import {MainTelemetryService} from 'vs/platform/telemetry/browser/mainTelemetryService';
import {ITelemetryService, ITelemetryInfo, ITelemetryServiceConfig} from 'vs/platform/telemetry/common/telemetry';
import {IStorageService} from 'vs/platform/storage/common/storage';
import errors = require('vs/base/common/errors');
import uuid = require('vs/base/common/uuid');
import {IConfigurationRegistry, Extensions} from 'vs/platform/configuration/common/configurationRegistry';
import {IConfigurationService, IConfigurationServiceEvent, ConfigurationServiceEventTypes} from 'vs/platform/configuration/common/configuration';
import {Registry} from 'vs/platform/platform';
const TELEMETRY_SECTION_ID = 'telemetry';
class StorageKeys {
public static MachineId: string = 'telemetry.machineId';
public static InstanceId: string = 'telemetry.instanceId';
}
interface ITelemetryEvent {
eventName: string;
data?: any;
}
export class ElectronTelemetryService extends MainTelemetryService implements ITelemetryService {
private static MAX_BUFFER_SIZE = 100;
private _setupIds: Promise<ITelemetryInfo>;
private _buffer: ITelemetryEvent[];
private _optInStatusLoaded: boolean;
constructor( @IStorageService private storageService: IStorageService, config?: TelemetryServiceConfig) {
constructor(@IConfigurationService private configurationService: IConfigurationService, @IStorageService private storageService: IStorageService, config?: ITelemetryServiceConfig) {
super(config);
this._buffer = [];
this._optInStatusLoaded = false;
this.loadOptinSettings();
this._setupIds = this.setupIds();
}
......@@ -33,6 +53,40 @@ export class ElectronTelemetryService extends MainTelemetryService implements IT
public getTelemetryInfo(): Promise<ITelemetryInfo> {
return this._setupIds;
}
/**
* override the base publicLog to prevent reporting any events before the optIn status is read from configuration
*/
public publicLog(eventName:string, data?: any): void {
if (this._optInStatusLoaded) {
super.publicLog(eventName, data);
} else {
// in case loading configuration is delayed, make sure the buffer does not grow beyond MAX_BUFFER_SIZE
if (this._buffer.length > ElectronTelemetryService.MAX_BUFFER_SIZE) {
this._buffer = [];
}
this._buffer.push({eventName: eventName, data: data});
}
}
private flushBuffer(): void {
let event: ITelemetryEvent = null;
while(event = this._buffer.pop()) {
super.publicLog(event.eventName, event.data);
}
}
private loadOptinSettings(): void {
this.configurationService.loadConfiguration(TELEMETRY_SECTION_ID).done(config => {
this.config.userOptIn = config ? config.enableTelemetry : this.config.userOptIn;
this._optInStatusLoaded = true;
this.publicLog('optInStatus', {optIn: this.config.userOptIn});
this.flushBuffer();
});
this.toUnbind.push(this.configurationService.addListener(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => {
this.config.userOptIn = e.config && e.config[TELEMETRY_SECTION_ID] ? e.config[TELEMETRY_SECTION_ID].enableTelemetry : this.config.userOptIn;
}));
}
private setupIds(): Promise<ITelemetryInfo> {
return Promise.all([this.setupInstanceId(), this.setupMachineId()]).then(() => {
......@@ -89,4 +143,19 @@ export class ElectronTelemetryService extends MainTelemetryService implements IT
}
}
}
\ No newline at end of file
}
const configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': TELEMETRY_SECTION_ID,
'order': 20,
'type': 'object',
'title': nls.localize('telemetryConfigurationTitle', "Telemetry configuration"),
'properties': {
'telemetry.enableTelemetry': {
'type': 'boolean',
'description': nls.localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to Microsoft."),
'default': true
}
}
});
\ No newline at end of file
......@@ -16,6 +16,8 @@ import Platform = require('vs/platform/platform');
import * as sinon from 'sinon';
import {createSyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
const optInStatusEventName: string = 'optInStatus';
class TestTelemetryAppender implements TelemetryService.ITelemetryAppender {
public events: any[];
......@@ -745,4 +747,52 @@ suite('TelemetryService', () => {
assert.equal(service.getSessionId(), testSessionId);
service.dispose();
}));
test('Telemetry Service respects user opt-in settings', sinon.test(function() {
let service = new MainTelemetryService.MainTelemetryService({userOptIn: false, enableTelemetry: true});
let testAppender = new TestTelemetryAppender();
service.addTelemetryAppender(testAppender);
service.publicLog('testEvent');
assert.equal(testAppender.getEventsCount(), 0);
service.dispose();
}));
test('Telemetry Service dont send events when enableTelemetry is off even if user is optin', sinon.test(function() {
let service = new MainTelemetryService.MainTelemetryService({userOptIn: true, enableTelemetry: false});
let testAppender = new TestTelemetryAppender();
service.addTelemetryAppender(testAppender);
service.publicLog('testEvent');
assert.equal(testAppender.getEventsCount(), 0);
service.dispose();
}));
test('Telemetry Service sends events when enableTelemetry is on even user optin is on', sinon.test(function() {
let service = new MainTelemetryService.MainTelemetryService({userOptIn: true, enableTelemetry: true});
let testAppender = new TestTelemetryAppender();
service.addTelemetryAppender(testAppender);
service.publicLog('testEvent');
assert.equal(testAppender.getEventsCount(), 1);
service.dispose();
}));
test('Telemetry Service allows optin friendly events', sinon.test(function() {
let service = new MainTelemetryService.MainTelemetryService({userOptIn: false, enableTelemetry: true});
let testAppender = new TestTelemetryAppender();
service.addTelemetryAppender(testAppender);
service.publicLog('testEvent');
assert.equal(testAppender.getEventsCount(), 0);
service.publicLog(optInStatusEventName, {userOptIn: false});
assert.equal(testAppender.getEventsCount(), 1);
assert.equal(testAppender.events[0].eventName, optInStatusEventName);
assert.equal(testAppender.events[0].data.userOptIn, false);
service.dispose();
}));
});
\ No newline at end of file
......@@ -234,20 +234,20 @@ export class WorkbenchShell {
let disableWorkspaceStorage = this.configuration.env.pluginTestsPath || (!this.workspace && !this.configuration.env.pluginDevelopmentPath); // without workspace or in any plugin test, we use inMemory storage unless we develop a plugin where we want to preserve state
this.storageService = new Storage(this.contextService, window.localStorage, disableWorkspaceStorage ? inMemoryLocalStorageInstance : window.localStorage);
let configService = new ConfigurationService(
this.contextService,
eventService
);
// no telemetry in a window for plugin development!
let enableTelemetry = this.configuration.env.isBuilt && !this.configuration.env.pluginDevelopmentPath ? !!this.configuration.env.enableTelemetry : false;
this.telemetryService = new ElectronTelemetryService(this.storageService, { enableTelemetry: enableTelemetry, version: this.configuration.env.version, commitHash: this.configuration.env.commitHash });
this.telemetryService = new ElectronTelemetryService(configService, this.storageService, { enableTelemetry: enableTelemetry, version: this.configuration.env.version, commitHash: this.configuration.env.commitHash });
this.keybindingService = new WorkbenchKeybindingService(this.contextService, eventService, this.telemetryService, <any>window);
this.messageService = new MessageService(this.contextService, this.windowService, this.telemetryService, this.keybindingService);
this.keybindingService.setMessageService(this.messageService);
let configService = new ConfigurationService(
this.contextService,
eventService
);
let fileService = new FileService(
configService,
eventService,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册