From cedfe2cced624939482eac1f4477fa09ccc6fdc8 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Mon, 6 Apr 2020 11:47:59 +0200 Subject: [PATCH] Add time and timeEnd functionality --- .editorconfig | 1 + package.json | 2 +- src/base.ts | 590 ++++++++++++++++++++++++------------------- src/consolewriter.ts | 29 ++- src/test.ts | 48 ++-- 5 files changed, 384 insertions(+), 286 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6e966e0..1c81eed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,4 @@ +[*] charset = utf-8 indent_style = space indent_size = 3 diff --git a/package.json b/package.json index 56084da..906e252 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hibas123/logging", - "version": "2.1.4", + "version": "2.2.0", "description": "", "main": "out/index.js", "types": "out/index.d.ts", diff --git a/src/base.ts b/src/base.ts index 3c9cd1b..d5c77a2 100644 --- a/src/base.ts +++ b/src/base.ts @@ -1,334 +1,416 @@ import { Observable } from "@hibas123/utils"; import { ConsoleAdapter } from "./consolewriter"; import inspect from "./inspect"; -import { Adapter, LoggingTypes, Message, FormatConfig, DefaultFormatConfig, Format, FormatTypes, Colors, FormattedText, FormattedLine } from "./types"; +import { + Adapter, + LoggingTypes, + Message, + FormatConfig, + DefaultFormatConfig, + Format, + FormatTypes, + Colors, + FormattedText, + FormattedLine, +} from "./types"; +import Logging from "."; + +const browser = typeof window !== "undefined"; export function removeColors(text: string) { - text = text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ""); + text = text.replace( + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + "" + ); - // let index = text.indexOf("\x1b"); - // while (index >= 0) { - // text = text.substring(0, index) + text.substring(index + 5, text.length); - // index = text.indexOf("\x1b"); - // } - return text; + // let index = text.indexOf("\x1b"); + // while (index >= 0) { + // text = text.substring(0, index) + text.substring(index + 5, text.length); + // index = text.indexOf("\x1b"); + // } + return text; } export interface LoggingBaseOptions { - /** - * Name will be prefixed on Console output and added to logfiles, if not specified here - */ - name: string, - /** - * Prints output to console - */ - console: boolean; + /** + * Name will be prefixed on Console output and added to logfiles, if not specified here + */ + name: string; + /** + * Prints output to console + */ + console: boolean; } export class LoggingBase { + private _formatMap: FormatConfig = new DefaultFormatConfig(); - private _formatMap: FormatConfig = new DefaultFormatConfig(); + public set formatMap(value: FormatConfig) { + this._formatMap = value; + } - public set formatMap(value: FormatConfig) { - this._formatMap = value; - } + private adapter = new Set(); + private adapter_init: Promise[] = []; + private timerMap = new Map(); - private adapter = new Set(); - private adapter_init: Promise[] = []; + private messageObservable = new Observable(); + protected _name: string; - private messageObservable = new Observable(); - protected _name: string; + private _logLevel = LoggingTypes.Debug; + get logLevel() { + return this._logLevel; + } - private _logLevel = LoggingTypes.Debug; + set logLevel(value: LoggingTypes) { + this._logLevel = value; + } - get logLevel() { - return this._logLevel; - } + get name() { + return this._name; + } - set logLevel(value: LoggingTypes) { - this._logLevel = value; - } + constructor(options?: Partial | string) { + let opt: Partial; + if (!options) opt = {}; + else if (typeof options === "string") { + opt = { name: options }; + } else { + opt = options; + } - get name() { - return this._name; - } + let config: LoggingBaseOptions = { + name: undefined, + console: true, + ...opt, + }; - constructor(options?: Partial | string) { - let opt: Partial; - if (!options) opt = {} - else if (typeof options === "string") { - opt = { name: options }; - } else { - opt = options; - } + if (config.name) this._name = config.name; - let config: LoggingBaseOptions = { - name: undefined, - console: true, - ...opt - }; + for (let key in this) { + if (typeof this[key] === "function") + this[key] = (this[key]).bind(this); + } - if (config.name) - this._name = config.name; + if (config.console) { + this.addAdapter(new ConsoleAdapter()); + } - for (let key in this) { - if (typeof this[key] === "function") this[key] = (this[key]).bind(this); - } + //Binding function to this + this.debug = this.debug.bind(this); + this.log = this.log.bind(this); + this.warn = this.warn.bind(this); + this.warning = this.warning.bind(this); + this.error = this.error.bind(this); + this.errorMessage = this.errorMessage.bind(this); + this.flush = this.flush.bind(this); + } - if (config.console) { - this.addAdapter(new ConsoleAdapter()); - } + addAdapter(adapter: Adapter) { + if (!this.adapter.has(adapter)) { + this.adapter.add(adapter); + let prms = Promise.resolve( + adapter.init(this.messageObservable.getPublicApi(), this._name) + ); + this.adapter_init.push(prms); + } + } + flush(sync: true): void; + flush(sync: false): Promise; + flush(sync: boolean): void | Promise { + if (sync) { + this.adapter.forEach((elm) => elm.flush(true)); + } else { + let adapters: (void | Promise)[] = []; + this.adapter.forEach((elm) => adapters.push(elm.flush(false))); + return Promise.all(adapters).then(() => {}); + } + } - //Binding function to this - this.debug = this.debug.bind(this); - this.log = this.log.bind(this); - this.warn = this.warn.bind(this); - this.warning = this.warning.bind(this); - this.error = this.error.bind(this); - this.errorMessage = this.errorMessage.bind(this); - this.flush = this.flush.bind(this); - } + public close() { + this.adapter.forEach((adapter) => + adapter.close ? adapter.close() : undefined + ); + } - addAdapter(adapter: Adapter) { - if (!this.adapter.has(adapter)) { - this.adapter.add(adapter); - let prms = Promise.resolve(adapter.init(this.messageObservable.getPublicApi(), this._name)); - this.adapter_init.push(prms); - } - } + public waitForSetup() { + return Promise.all(this.adapter_init); + } - flush(sync: true): void; - flush(sync: false): Promise; - flush(sync: boolean): void | Promise { - if (sync) { - this.adapter.forEach(elm => elm.flush(true)); - } else { - let adapters: (void | Promise)[] = []; - this.adapter.forEach(elm => adapters.push(elm.flush(false))); - return Promise.all(adapters).then(() => { }); - } - } + debug(...message: any[]) { + if (this._logLevel <= LoggingTypes.Debug) + this.message(LoggingTypes.Debug, message); + } - public close() { - this.adapter.forEach(adapter => adapter.close ? adapter.close() : undefined); - } + log(...message: any[]) { + if (this._logLevel <= LoggingTypes.Log) + this.message(LoggingTypes.Log, message); + } - public waitForSetup() { - return Promise.all(this.adapter_init); - } + warning(...message: any[]) { + if (this._logLevel <= LoggingTypes.Warning) + this.message(LoggingTypes.Warning, message); + } - debug(...message: any[]) { - if (this._logLevel <= LoggingTypes.Debug) - this.message(LoggingTypes.Debug, message); - } + warn(...message: any[]) { + if (this._logLevel <= LoggingTypes.Warning) + this.message(LoggingTypes.Warning, message); + } - log(...message: any[]) { - if (this._logLevel <= LoggingTypes.Log) - this.message(LoggingTypes.Log, message); - } + error(error: Error | string) { + if (this._logLevel > LoggingTypes.Error) return; + if (!error) + error = "Empty ERROR was passed, so no informations available"; + if (typeof error === "string") { + let e = new Error("This is a fake error, to get a stack trace"); + this.message(LoggingTypes.Error, [error, "\n", e.stack]); + } else { + this.message( + LoggingTypes.Error, + [error.message, "\n", error.stack], + getCallerFromExisting(error) + ); + } + } - warning(...message: any[]) { - if (this._logLevel <= LoggingTypes.Warning) - this.message(LoggingTypes.Warning, message); - } + errorMessage(...message: any[]) { + if (this._logLevel <= LoggingTypes.Error) + this.message(LoggingTypes.Error, message); + } - warn(...message: any[]) { - if (this._logLevel <= LoggingTypes.Warning) - this.message(LoggingTypes.Warning, message); - } + protected getCurrentTime() { + if (browser && window.performance && window.performance.now) { + return window.performance.now(); + } else { + return Date.now(); + } + } - error(error: Error | string) { - if (this._logLevel > LoggingTypes.Error) - return; - if (!error) error = "Empty ERROR was passed, so no informations available"; - if (typeof error === "string") { - let e = new Error("This is a fake error, to get a stack trace"); - this.message(LoggingTypes.Error, [error, "\n", e.stack]); - } else { - this.message(LoggingTypes.Error, [error.message, "\n", error.stack], getCallerFromExisting(error)); - } - } + /** + * The time difference in milliseconds (fractions allowed!) + * @param start Start time from getCurrentTime + */ + protected getTimeDiff(start: any) { + if (browser && window.performance && window.performance.now) { + return window.performance.now() - start; + } else { + return Date.now() - start; + } + } - errorMessage(...message: any[]) { - if (this._logLevel <= LoggingTypes.Error) - this.message(LoggingTypes.Error, message); - } + time(id?: string, name = id) { + if (!id) { + id = Math.floor(Math.random() * 899999 + 100000).toString(); + } - private message(type: LoggingTypes, message: any[], caller?: { file: string, line: number }) { - let date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); + this.timerMap.set(id, { + name, + start: this.getCurrentTime(), + }); - let file_raw = caller || getCallerFile(); - let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`; + return { + id, + end: () => this.timeEnd(id), + }; + } - let type_str = LoggingTypes[type].toUpperCase().padEnd(5, " "); - let type_format: Format[] = []; - switch (type) { - case LoggingTypes.Log: - type_format = this._formatMap.log; - break; - case LoggingTypes.Error: - type_format = this._formatMap.error; - break; - case LoggingTypes.Debug: - type_format = this._formatMap.debug; - break; - case LoggingTypes.Warning: - type_format = this._formatMap.warning; - break; - } + timeEnd(id: string) { + let timer = this.timerMap.get(id); + if (timer) { + let diff = this.getTimeDiff(timer.start); + Logging.message(LoggingTypes.Debug, [ + withColor(Colors.GREEN, `[${timer.name}]`), + `->`, + withColor(Colors.BLUE, diff.toFixed(4)), + "ms", + ]); + } + } - const prefix: FormattedText[] = []; - const a = (text: string, formats: Format[] = []) => { - prefix.push({ - text, - formats - }) - } + private message( + type: LoggingTypes, + message: any[], + caller?: { file: string; line: number } + ) { + let date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, ""); - a("["); - a(date, this._formatMap.date); - a("]["); - a(type_str, type_format); - if (file_raw.file) { - a("]["); - a(file, this._formatMap.file); - } - a("]: "); + let file_raw = caller || getCallerFile(); + let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`; + let type_str = LoggingTypes[type].toUpperCase().padEnd(5, " "); + let type_format: Format[] = []; + switch (type) { + case LoggingTypes.Log: + type_format = this._formatMap.log; + break; + case LoggingTypes.Error: + type_format = this._formatMap.error; + break; + case LoggingTypes.Debug: + type_format = this._formatMap.debug; + break; + case LoggingTypes.Warning: + type_format = this._formatMap.warning; + break; + } - let raw: string[] = []; + const prefix: FormattedText[] = []; + const a = (text: string, formats: Format[] = []) => { + prefix.push({ + text, + formats, + }); + }; - const formatted: FormattedLine[] = []; - let line: FormattedLine; - const newLine = () => { - if (line && line.length > 0) { - formatted.push(line); - raw.push(line.map(e => e.text).join("")); + a("["); + a(date, this._formatMap.date); + a("]["); + a(type_str, type_format); + if (file_raw.file) { + a("]["); + a(file, this._formatMap.file); + } + a("]: "); + + let raw: string[] = []; + + const formatted: FormattedLine[] = []; + let line: FormattedLine; + const newLine = () => { + if (line && line.length > 0) { + formatted.push(line); + raw.push(line.map((e) => e.text).join("")); + } + line = [...prefix]; + }; + newLine(); + + message.forEach((e, i) => { + let formats: Format[] = []; + if (typeof e !== "string") { + if (e && typeof e === "object") { + if (e[colorSymbol]) { + formats.push({ + type: FormatTypes.COLOR, + color: e[colorSymbol], + }); + e = e.value; + } } - line = [...prefix]; - } - newLine(); + if (typeof e !== "string") + e = inspect(e, { + colors: true, + showHidden: true, + depth: 3, + }) as string; + } - message.forEach((e, i) => { - let formats: Format[] = []; - if (typeof e !== "string") { - if (e && typeof e === "object") { - if (e[colorSymbol]) { - formats.push({ - type: FormatTypes.COLOR, - color: e[colorSymbol] - }) - e = e.value; - } - } - if (typeof e !== "string") - e = inspect(e, { colors: true, showHidden: true, depth: 3 }) as string; - } + removeColors(e) + .split("\n") + .map((text, index, { length }) => { + line.push({ text, formats }); + if (index < length - 1) { + newLine(); + } + }); - removeColors(e).split("\n").map((text, index, { length }) => { - line.push({ text, formats }); - if (index < length - 1) { - newLine(); - } - }) + if (!e.endsWith("\n") && i < message.length - 1) { + line.push({ text: " ", formats: [] }); + } + }); - if (!e.endsWith("\n") && i < message.length - 1) { - line.push({ text: " ", formats: [] }); - } - }); + newLine(); - newLine(); + let msg: Message = { + date: new Date(), + file, + name: this._name, + text: { + raw, + formatted, + }, + type, + }; - let msg: Message = { - date: new Date(), - file, - name: this._name, - text: { - raw, - formatted - }, - type - } - - this.messageObservable.send(msg); - } + this.messageObservable.send(msg); + } } const colorSymbol = Symbol("color"); export interface ColorFormat { - [colorSymbol]: Colors; - value: any; + [colorSymbol]: Colors; + value: any; } export function withColor(color: Colors, value: any): ColorFormat { - return { - [colorSymbol]: color, - value - } + return { + [colorSymbol]: color, + value, + }; } function getStack() { - // Save original Error.prepareStackTrace - let origPrepareStackTrace = (Error).prepareStackTrace; + // Save original Error.prepareStackTrace + let origPrepareStackTrace = (Error).prepareStackTrace; - // Override with function that just returns `stack` - (Error).prepareStackTrace = function (_, stack) { - return stack - } + // Override with function that just returns `stack` + (Error).prepareStackTrace = function (_, stack) { + return stack; + }; - // Create a new `Error`, which automatically gets `stack` - let err = new Error(); + // Create a new `Error`, which automatically gets `stack` + let err = new Error(); - // Evaluate `err.stack`, which calls our new `Error.prepareStackTrace` - let stack: any[] = err.stack; + // Evaluate `err.stack`, which calls our new `Error.prepareStackTrace` + let stack: any[] = err.stack; - // Restore original `Error.prepareStackTrace` - (Error).prepareStackTrace = origPrepareStackTrace; + // Restore original `Error.prepareStackTrace` + (Error).prepareStackTrace = origPrepareStackTrace; - // Remove superfluous function call on stack - stack.shift(); // getStack --> Error + // Remove superfluous function call on stack + stack.shift(); // getStack --> Error - return stack + return stack; } function baseName(path) { - return path.split(/[\\/]/).pop(); + return path.split(/[\\/]/).pop(); } function getCallerFile() { - try { - let stack = getStack() + try { + let stack = getStack(); - let current_file = stack.shift().getFileName(); + let current_file = stack.shift().getFileName(); - while (stack.length) { - let caller_file = stack.shift(); - if (current_file !== caller_file.getFileName()) - return { - file: baseName(caller_file.getFileName()), - line: caller_file.getLineNumber() - }; - } - } catch (err) { } - return { file: undefined, line: 0 }; + while (stack.length) { + let caller_file = stack.shift(); + if (current_file !== caller_file.getFileName()) + return { + file: baseName(caller_file.getFileName()), + line: caller_file.getLineNumber(), + }; + } + } catch (err) {} + return { file: undefined, line: 0 }; } -function getCallerFromExisting(err: Error): { file: string, line: number } { - if (!err || !err.stack) return { file: "NOFILE", line: 0 }; - let lines = err.stack.split("\n"); - lines.shift();// removing first line - while (lines.length > 0) { - let line = lines.shift(); - let matches = line.match(/[a-zA-Z_-]+[.][a-zA-Z_-]+[:][0-9]+/g) - if (matches && matches.length > 0) { - let [f, line] = matches[0].split(":") - return { - file: f, line: Number(line) - }; - } - } -} \ No newline at end of file +function getCallerFromExisting(err: Error): { file: string; line: number } { + if (!err || !err.stack) return { file: "NOFILE", line: 0 }; + let lines = err.stack.split("\n"); + lines.shift(); // removing first line + while (lines.length > 0) { + let line = lines.shift(); + let matches = line.match(/[a-zA-Z_-]+[.][a-zA-Z_-]+[:][0-9]+/g); + if (matches && matches.length > 0) { + let [f, line] = matches[0].split(":"); + return { + file: f, + line: Number(line), + }; + } + } +} diff --git a/src/consolewriter.ts b/src/consolewriter.ts index 791a9cf..fa89918 100644 --- a/src/consolewriter.ts +++ b/src/consolewriter.ts @@ -1,6 +1,12 @@ import { ObservableInterface } from "@hibas123/utils"; import { Colors } from "./index"; -import { Adapter, Message, FormattedLine, TerminalFormats, FormatTypes } from "./types"; +import { + Adapter, + Message, + FormattedLine, + TerminalFormats, + FormatTypes, +} from "./types"; const browser = typeof window !== "undefined"; @@ -9,7 +15,7 @@ export class ConsoleAdapter implements Adapter { observable.subscribe(this.onMessage.bind(this)); } - flush() { } + flush() {} // TODO: Check if required! // private escape(text: string): string { @@ -128,20 +134,21 @@ export class ConsoleAdapter implements Adapter { if (message.name) prefix = `[${message.name}]=>`; if (browser) { - let formats: string[] = []; - let text = lines.map(line => { - let [t, fmts] = this.formatLine(line); - formats.push(...fmts); - return t; - }).join("\n"); + let text = lines + .map((line) => { + let [t, fmts] = this.formatLine(line); + formats.push(...fmts); + return t; + }) + .join("\n"); // console.log(formats); console.log(text, ...formats); } else { - lines.forEach(line => { + lines.forEach((line) => { let [text] = this.formatLine(line); console.log(prefix + text); - }) + }); } } -} \ No newline at end of file +} diff --git a/src/test.ts b/src/test.ts index 64c2b62..429ceda 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,39 +1,44 @@ import { Logging, LoggingBase, LoggingTypes, Colors, withColor } from "."; -Logging.log("test") +Logging.log("test"); Logging.log("i", "am", { a: "an" }, 1000); Logging.error(new Error("fehler 001")); Logging.debug("Some Debug infos"); Logging.errorMessage("i", "am", "an", "error"); -Logging.log("\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m TEST \x1b[31m\x1b[31m\x1b[31m") +Logging.log( + "\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m TEST \x1b[31m\x1b[31m\x1b[31m" +); -Logging.log(withColor(Colors.MAGENTA, "This text should be magenta!"), "This not!") -Logging.log(withColor(Colors.MAGENTA, { somekey: "Some value" })) +Logging.log( + withColor(Colors.MAGENTA, "This text should be magenta!"), + "This not!" +); +Logging.log(withColor(Colors.MAGENTA, { somekey: "Some value" })); -let err = new Error() -if (typeof err.stack !== "string") console.log("Stacktrace invalid", err.stack) +let err = new Error(); +if (typeof err.stack !== "string") console.log("Stacktrace invalid", err.stack); let cus = new LoggingBase({ name: "test" }); -cus.log("Hello from custom Logger") +cus.log("Hello from custom Logger"); cus.log("This has some %c symbols inside of it!"); let cus2 = new LoggingBase("test2"); -cus2.log("Hello from custom Logger 2") +cus2.log("Hello from custom Logger 2"); let cus22 = new LoggingBase("test2"); -cus22.log("Hello from custom Logger 22") -cus2.log("Hello from custom Logger 2") -cus22.log("Hello from custom Logger 22") -cus2.log("Hello from custom Logger 2") -cus22.log("Hello from custom Logger 22") -cus2.log("Hello from custom Logger 2") -cus22.log("Hello from custom Logger 22") -cus2.log("Hello from custom Logger 2") -cus22.log("Hello from custom Logger 22") -cus2.log("Hello from custom Logger 2") +cus22.log("Hello from custom Logger 22"); +cus2.log("Hello from custom Logger 2"); +cus22.log("Hello from custom Logger 22"); +cus2.log("Hello from custom Logger 2"); +cus22.log("Hello from custom Logger 22"); +cus2.log("Hello from custom Logger 2"); +cus22.log("Hello from custom Logger 22"); +cus2.log("Hello from custom Logger 2"); +cus22.log("Hello from custom Logger 22"); +cus2.log("Hello from custom Logger 2"); -Logging.debug("Only Errors should appear:") +Logging.debug("Only Errors should appear:"); Logging.logLevel = LoggingTypes.Error; Logging.debug("This should not be there 1"); @@ -42,4 +47,7 @@ Logging.warn("This should not be there 3"); Logging.warning("This should not be there 4"); Logging.error("This should be there 1"); -Logging.errorMessage("This should be there 2"); \ No newline at end of file +Logging.errorMessage("This should be there 2"); + +const timer = Logging.time("timer1", "Test Timer"); +setTimeout(() => timer.end(), 1000);