diff --git a/README.md b/README.md index 19a2db4..974b8f7 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,15 @@ 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 => { // TODO: initialize - t.sendTelemetryEvent(“initializeDone”); + TelemetryWrapper.sendTelemetryEvent(“initializeDone”); // TODO: pre tasks - t.sendTelemetryEvent("preTasksDone"); + TelemetryWrapper.sendTelemetryEvent("preTasksDone"); // TODO: final tasks } - }); + ); } ``` @@ -79,31 +79,32 @@ Result: ``` export function activate(context: vscode.ExtensionContext): void { - TelemetryWrapper.registerCommand("commandName", (t: Session) => { - return (args: any[]): void => { + TelemetryWrapper.registerCommand("commandName", + (args: any[]): void => { // TODO: initialize - t.info(“initializeDone”); + TelemetryWrapper.info(“initializeDone”); // TODO: pre tasks with error - t.error("preTasksNotDone"); + TelemetryWrapper.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 +113,7 @@ export function activate(context: vscode.ExtensionContext): void { // TODO: final tasks t.extraProperties.finishedSteps.push("finalTasks"); } - }); + ); } ``` diff --git a/package.json b/package.json index ff87b8f..c73a23b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vscode-extension-telemetry-wrapper", - "version": "0.1.3", + "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/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 09fb15a..1f52f1d 100644 --- a/src/TelemetryWrapper.ts +++ b/src/TelemetryWrapper.ts @@ -3,10 +3,15 @@ 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"; +import { LogLevel } from './LogLevel'; + +const SESSION_KEY: string = "session"; export module TelemetryWrapper { let reporter: TelemetryReporter; + let sessionNamespace: Namespace; export async function initilizeFromJsonFile(fsPath: string): Promise { if (await fse.pathExists(fsPath)) { @@ -25,23 +30,29 @@ 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_KEY, session); + report(EventType.COMMAND_START, { + properties: Object.assign({}, session.getCustomEvent().properties), + measures: { logLevel: LogLevel.INFO } + }); + try { + await callback(param); + } catch (error) { + fatal(error, ExitCode.GENERAL_ERROR); + throw error; + } finally { + endSession(session); + } }); - const callback: (...args: any[]) => any = task(session); - try { - await callback(param); - } catch (error) { - session.fatal(error, ExitCode.GENERAL_ERROR); - throw error; - } finally { - session.end(); - } }); } @@ -54,12 +65,86 @@ 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 }) + }); } } + export function currentSession() { + 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", @@ -70,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); + } + } }