From ccf8b850db22d1e36520f5f6c2ab6ca39e18ab1d Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Wed, 17 Jan 2018 17:38:32 +0800 Subject: [PATCH 1/4] store session for each command --- package.json | 4 +++- src/TelemetryWrapper.ts | 39 +++++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 82b0bee..c73a23b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vscode-extension-telemetry-wrapper", - "version": "0.1.2", + "version": "0.2.0-dev", "description": "A module to auto send telemetry for each registered command, using vscode-extension-telemetry.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -20,11 +20,13 @@ "url": "https://github.com/Eskibear/vscode-extension-telemetry-wrapper.git" }, "dependencies": { + "continuation-local-storage": "^3.2.1", "fs-extra": "^5.0.0", "uuid": "^3.1.0", "vscode-extension-telemetry": "0.0.10" }, "devDependencies": { + "@types/continuation-local-storage": "^3.2.1", "@types/fs-extra": "^5.0.0", "@types/node": "^8.5.7", "@types/uuid": "^3.4.3", diff --git a/src/TelemetryWrapper.ts b/src/TelemetryWrapper.ts index c885851..58909cf 100644 --- a/src/TelemetryWrapper.ts +++ b/src/TelemetryWrapper.ts @@ -3,10 +3,12 @@ import * as vscode from "vscode"; import TelemetryReporter from "vscode-extension-telemetry"; import { Session } from "./Session"; import { ICustomEvent } from "./Interfaces"; -import { ExitCode } from './ExitCode'; +import { ExitCode } from "./ExitCode"; +import { createNamespace, Namespace } from "continuation-local-storage"; export module TelemetryWrapper { let reporter: TelemetryReporter; + let sessionNamespace: Namespace; export async function initilizeFromJsonFile(fsPath: string): Promise { if (await fse.pathExists(fsPath)) { @@ -25,23 +27,28 @@ export module TelemetryWrapper { reporter = new TelemetryReporter(`${publisher}.${name}`, version, aiKey); report(EventType.ACTIVATION); } + if (!sessionNamespace) { + sessionNamespace = createNamespace("sessionNamespace"); + } } - export function registerCommand(command: string, task: (currentSession?: Session) => (...args: any[]) => any): vscode.Disposable { + export function registerCommand(command: string, callback: (...args: any[]) => any): vscode.Disposable { return vscode.commands.registerCommand(command, async (param: any[]) => { - const session: Session = startSession(command); - report(EventType.COMMAND_START, { - properties: Object.assign({}, session.getCustomEvent().properties) + sessionNamespace.run(async () => { + const session: Session = startSession(command); + sessionNamespace.set("session", session); + report(EventType.COMMAND_START, { + properties: Object.assign({}, session.getCustomEvent().properties) + }); + try { + await callback(param); + } catch (error) { + session.fatal(error, ExitCode.GENERAL_ERROR); + throw error; + } finally { + session.end(); + } }); - const callback: (...args: any[]) => any = task(session); - try { - await callback(param); - } catch (error) { - session.fatal(error, ExitCode.GENERAL_ERROR); - throw error; - } finally { - session.end(); - } }); } @@ -60,6 +67,10 @@ export module TelemetryWrapper { } } + export function getSession() { + return sessionNamespace && sessionNamespace.get("session"); + } + export enum EventType { ACTIVATION = "activation", ERROR = "error", From fc98d8d0b2ba4da50b4600dfb5610115389f88b4 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Thu, 18 Jan 2018 10:43:30 +0800 Subject: [PATCH 2/4] change API --- README.md | 33 ++++++++++++++++++--------------- src/TelemetryWrapper.ts | 8 +++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 19a2db4..755cdb1 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,11 @@ export function activate(context: vscode.ExtensionContext): void { ``` export function activate(context: vscode.ExtensionContext): void { - TelemetryWrapper.registerCommand("commandName", () => { - return (args: any[]): void => { + TelemetryWrapper.registerCommand("commandName", + (args: any[]): void => { // TODO } - }); + ); } ``` @@ -54,15 +54,16 @@ export function activate(context: vscode.ExtensionContext): void { ``` export function activate(context: vscode.ExtensionContext): void { - TelemetryWrapper.registerCommand("commandName", (t: Session) => { - return (args: any[]): void => { + TelemetryWrapper.registerCommand("commandName", + (args: any[]): void => { + const t = TelemetryWrapper.currentSession(); // TODO: initialize t.sendTelemetryEvent(“initializeDone”); // TODO: pre tasks t.sendTelemetryEvent("preTasksDone"); // TODO: final tasks } - }); + ); } ``` @@ -79,31 +80,33 @@ Result: ``` export function activate(context: vscode.ExtensionContext): void { - TelemetryWrapper.registerCommand("commandName", (t: Session) => { - return (args: any[]): void => { + TelemetryWrapper.registerCommand("commandName", + (args: any[]): void => { + const t = TelemetryWrapper.currentSession(); // TODO: initialize t.info(“initializeDone”); // TODO: pre tasks with error t.error("preTasksNotDone"); // TODO: final tasks } - }); + ); } ``` Result: * publisher.extension/commandStart {sessionId: xxx} -* publisher.extension/info {message: "initilizeDone", sessionId: xxx} -* publisher.extension/error {message: "preTasksDone", sessionId: xxx} -* publisher.extension/commandEnd {sessionId: xxx, exitCode: 255} +* publisher.extension/info {message: "initilizeDone", logLevel: 400, sessionId: xxx} +* publisher.extension/error {message: "preTasksDone", logLevel: 200, sessionId: xxx} +* publisher.extension/commandEnd {sessionId: xxx, exitCode: 1} **Inject customized properties into the a session** ``` export function activate(context: vscode.ExtensionContext): void { - TelemetryWrapper.registerCommand("commandName", (t: Session) => { - return (args: any[]): void => { + TelemetryWrapper.registerCommand("commandName", + (args: any[]): void => { + const t = TelemetryWrapper.currentSession(); t.extraProperties.finishedSteps = []; // TODO: initialize t.extraProperties.finishedSteps.push("initialize"); @@ -112,7 +115,7 @@ export function activate(context: vscode.ExtensionContext): void { // TODO: final tasks t.extraProperties.finishedSteps.push("finalTasks"); } - }); + ); } ``` diff --git a/src/TelemetryWrapper.ts b/src/TelemetryWrapper.ts index 58909cf..fba4403 100644 --- a/src/TelemetryWrapper.ts +++ b/src/TelemetryWrapper.ts @@ -6,6 +6,8 @@ import { ICustomEvent } from "./Interfaces"; import { ExitCode } from "./ExitCode"; import { createNamespace, Namespace } from "continuation-local-storage"; +const SESSION_KEY: string = "session"; + export module TelemetryWrapper { let reporter: TelemetryReporter; let sessionNamespace: Namespace; @@ -36,7 +38,7 @@ export module TelemetryWrapper { return vscode.commands.registerCommand(command, async (param: any[]) => { sessionNamespace.run(async () => { const session: Session = startSession(command); - sessionNamespace.set("session", session); + sessionNamespace.set(SESSION_KEY, session); report(EventType.COMMAND_START, { properties: Object.assign({}, session.getCustomEvent().properties) }); @@ -67,8 +69,8 @@ export module TelemetryWrapper { } } - export function getSession() { - return sessionNamespace && sessionNamespace.get("session"); + export function currentSession() { + return sessionNamespace && sessionNamespace.get(SESSION_KEY); } export enum EventType { From 0d28aaf74a3b654952357ef3c88a3e36ffbee015 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Thu, 25 Jan 2018 10:27:25 +0800 Subject: [PATCH 3/4] decouple Session and Telemetry --- src/Session.ts | 62 +--------------------------- src/TelemetryWrapper.ts | 90 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/Session.ts b/src/Session.ts index 26f1f60..402dc07 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -1,7 +1,6 @@ import * as uuid from "uuid"; import { ICustomEvent } from "./Interfaces"; import { TelemetryWrapper } from "./TelemetryWrapper"; -import { LogLevel } from "./LogLevel"; import { ExitCode } from "./ExitCode"; export class Session { @@ -25,71 +24,12 @@ export class Session { const extraPropertiesObject = Object.assign({}, ...Object.keys(this.extraProperties).map(k => ({[`extra.${k}`]: this.extraProperties[k]}))); const extraMeasuresObject = Object.assign({}, ...Object.keys(this.extraMeasures).map(k => ({[`extra.${k}`]: this.extraMeasures[k]}))); ret.properties = Object.assign({}, extraPropertiesObject, { sessionId: this.id, action: this.action, startAt: this.startAt }); - ret.measures = Object.assign({}, extraMeasuresObject, { duration: (this.stopAt || new Date()).getTime() - this.startAt.getTime() }, { logLevel: LogLevel.INFO }); + ret.measures = Object.assign({}, extraMeasuresObject, { duration: (this.stopAt || new Date()).getTime() - this.startAt.getTime() }); return ret; } public end(): void { this.stopAt = new Date(); this.exitCode = this.exitCode || ExitCode.SUCCESS; - const customEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.COMMAND_END, { - properties: Object.assign({}, customEvent.properties, { stopAt: this.stopAt, exitCode: this.exitCode }), - measures: Object.assign({}, customEvent.measures) - }); - } - - public fatal(message: any, exitCode?: string): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.FATAL, { - properties: Object.assign({}, customEvent.properties, { message }), - measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.FATAL }) - }); - this.exitCode = exitCode || ExitCode.GENERAL_ERROR; - } - - public error(message: any, exitCode?: string): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.ERROR, { - properties: Object.assign({}, customEvent.properties, { message }), - measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.ERROR }) - }); - this.exitCode = exitCode || ExitCode.GENERAL_ERROR; - } - - public info(message: any): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.INFO, { - properties: Object.assign({}, customEvent.properties, { message }), - measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.INFO }) - }); - } - - public warning(message: any): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.WARN, { - properties: Object.assign({}, customEvent.properties, { message }), - measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.WARN }) - }); - } - - public verbose(message: any): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(TelemetryWrapper.EventType.VERBOSE, { - properties: Object.assign({}, customEvent.properties, { message }), - measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.VERBOSE }) - }); - } - - public sendTelemetryEvent(eventName: string, properties?: { - [key: string]: string; - }, measures?: { - [key: string]: number; - }): void { - const customEvent: ICustomEvent = this.getCustomEvent(); - TelemetryWrapper.report(eventName, { - properties: Object.assign({}, properties, customEvent.properties), - measures: Object.assign({}, measures, customEvent.measures) - }); } } diff --git a/src/TelemetryWrapper.ts b/src/TelemetryWrapper.ts index 3cd21bb..1f52f1d 100644 --- a/src/TelemetryWrapper.ts +++ b/src/TelemetryWrapper.ts @@ -5,6 +5,7 @@ import { Session } from "./Session"; import { ICustomEvent } from "./Interfaces"; import { ExitCode } from "./ExitCode"; import { createNamespace, Namespace } from "continuation-local-storage"; +import { LogLevel } from './LogLevel'; const SESSION_KEY: string = "session"; @@ -40,15 +41,16 @@ export module TelemetryWrapper { const session: Session = startSession(command); sessionNamespace.set(SESSION_KEY, session); report(EventType.COMMAND_START, { - properties: Object.assign({}, session.getCustomEvent().properties) + properties: Object.assign({}, session.getCustomEvent().properties), + measures: { logLevel: LogLevel.INFO } }); try { await callback(param); } catch (error) { - session.fatal(error, ExitCode.GENERAL_ERROR); + fatal(error, ExitCode.GENERAL_ERROR); throw error; } finally { - session.end(); + endSession(session); } }); }); @@ -63,9 +65,14 @@ export module TelemetryWrapper { return trans; } - export function report(eventType: EventType | string, event?: ICustomEvent): void { - if (reporter) { - reporter.sendTelemetryEvent(eventType, event && event.properties, event && event.measures); + export function endSession(session: Session) { + if (session) { + session.end(); + const customEvent = session.getCustomEvent(); + report(EventType.COMMAND_END, { + properties: Object.assign({}, customEvent.properties, { stopAt: session.stopAt, exitCode: session.exitCode }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.INFO }) + }); } } @@ -73,6 +80,71 @@ export module TelemetryWrapper { return sessionNamespace && sessionNamespace.get(SESSION_KEY); } + + export function fatal(message: any, exitCode?: string): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(EventType.ERROR, { + properties: Object.assign({}, customEvent.properties, { message }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.FATAL }) + }); + if (session) { + session.exitCode = exitCode || ExitCode.GENERAL_ERROR; + } + } + + export function error(message: any, exitCode?: string): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(EventType.ERROR, { + properties: Object.assign({}, customEvent.properties, { message }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.ERROR }) + }); + if (session) { + session.exitCode = exitCode || ExitCode.GENERAL_ERROR; + } + } + + export function info(message: any): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(EventType.INFO, { + properties: Object.assign({}, customEvent.properties, { message }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.INFO }) + }); + } + + export function warn(message: any): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(EventType.WARN, { + properties: Object.assign({}, customEvent.properties, { message }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.WARN }) + }); + } + + export function verbose(message: any): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(EventType.VERBOSE, { + properties: Object.assign({}, customEvent.properties, { message }), + measures: Object.assign({}, customEvent.measures, { logLevel: LogLevel.VERBOSE }) + }); + } + + export function sendTelemetryEvent(eventName: string, properties?: { + [key: string]: string; + }, measures?: { + [key: string]: number; + }): void { + const session: Session = currentSession(); + const customEvent: ICustomEvent = session ? session.getCustomEvent() : {}; + report(eventName, { + properties: Object.assign({}, properties, customEvent.properties), + measures: Object.assign({}, measures, customEvent.measures) + }); + } + export enum EventType { ACTIVATION = "activation", FATAL = "fatal", @@ -83,5 +155,11 @@ export module TelemetryWrapper { COMMAND_START = "commandStart", COMMAND_END = "commandEnd" } + + function report(eventType: EventType | string, event?: ICustomEvent): void { + if (reporter) { + reporter.sendTelemetryEvent(eventType, event && event.properties, event && event.measures); + } + } } From e61d3b581d550317075d33b40ec4cd4dad308253 Mon Sep 17 00:00:00 2001 From: Yan Zhang Date: Tue, 30 Jan 2018 14:55:54 +0800 Subject: [PATCH 4/4] update README for new APIs --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 755cdb1..974b8f7 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,10 @@ export function activate(context: vscode.ExtensionContext): void { TelemetryWrapper.registerCommand("commandName", (args: any[]): void => { - const t = TelemetryWrapper.currentSession(); // TODO: initialize - t.sendTelemetryEvent(“initializeDone”); + TelemetryWrapper.sendTelemetryEvent(“initializeDone”); // TODO: pre tasks - t.sendTelemetryEvent("preTasksDone"); + TelemetryWrapper.sendTelemetryEvent("preTasksDone"); // TODO: final tasks } ); @@ -82,11 +81,10 @@ export function activate(context: vscode.ExtensionContext): void { TelemetryWrapper.registerCommand("commandName", (args: any[]): void => { - const t = TelemetryWrapper.currentSession(); // TODO: initialize - t.info(“initializeDone”); + TelemetryWrapper.info(“initializeDone”); // TODO: pre tasks with error - t.error("preTasksNotDone"); + TelemetryWrapper.error("preTasksNotDone"); // TODO: final tasks } );