Add time and timeEnd functionality

This commit is contained in:
Fabian Stamm 2020-04-06 11:47:59 +02:00
parent 675ec50139
commit cedfe2cced
5 changed files with 384 additions and 286 deletions

View File

@ -1,3 +1,4 @@
[*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 3 indent_size = 3

View File

@ -1,6 +1,6 @@
{ {
"name": "@hibas123/logging", "name": "@hibas123/logging",
"version": "2.1.4", "version": "2.2.0",
"description": "", "description": "",
"main": "out/index.js", "main": "out/index.js",
"types": "out/index.d.ts", "types": "out/index.d.ts",

View File

@ -1,334 +1,416 @@
import { Observable } from "@hibas123/utils"; import { Observable } from "@hibas123/utils";
import { ConsoleAdapter } from "./consolewriter"; import { ConsoleAdapter } from "./consolewriter";
import inspect from "./inspect"; 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) { 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"); // let index = text.indexOf("\x1b");
// while (index >= 0) { // while (index >= 0) {
// text = text.substring(0, index) + text.substring(index + 5, text.length); // text = text.substring(0, index) + text.substring(index + 5, text.length);
// index = text.indexOf("\x1b"); // index = text.indexOf("\x1b");
// } // }
return text; return text;
} }
export interface LoggingBaseOptions { export interface LoggingBaseOptions {
/** /**
* Name will be prefixed on Console output and added to logfiles, if not specified here * Name will be prefixed on Console output and added to logfiles, if not specified here
*/ */
name: string, name: string;
/** /**
* Prints output to console * Prints output to console
*/ */
console: boolean; console: boolean;
} }
export class LoggingBase { 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) { private adapter = new Set<Adapter>();
this._formatMap = value; private adapter_init: Promise<void>[] = [];
}
private timerMap = new Map<string, { name: string; start: any }>();
private adapter = new Set<Adapter>(); private messageObservable = new Observable<Message>();
private adapter_init: Promise<void>[] = []; protected _name: string;
private messageObservable = new Observable<Message>(); private _logLevel = LoggingTypes.Debug;
protected _name: string;
get logLevel() {
return this._logLevel;
}
private _logLevel = LoggingTypes.Debug; set logLevel(value: LoggingTypes) {
this._logLevel = value;
}
get logLevel() { get name() {
return this._logLevel; return this._name;
} }
set logLevel(value: LoggingTypes) { constructor(options?: Partial<LoggingBaseOptions> | string) {
this._logLevel = value; let opt: Partial<LoggingBaseOptions>;
} if (!options) opt = {};
else if (typeof options === "string") {
opt = { name: options };
} else {
opt = options;
}
get name() { let config: LoggingBaseOptions = {
return this._name; name: undefined,
} console: true,
...opt,
};
constructor(options?: Partial<LoggingBaseOptions> | string) { if (config.name) this._name = config.name;
let opt: Partial<LoggingBaseOptions>;
if (!options) opt = {}
else if (typeof options === "string") {
opt = { name: options };
} else {
opt = options;
}
let config: LoggingBaseOptions = { for (let key in this) {
name: undefined, if (typeof this[key] === "function")
console: true, this[key] = (<any>this[key]).bind(this);
...opt }
};
if (config.name) if (config.console) {
this._name = config.name; this.addAdapter(new ConsoleAdapter());
}
for (let key in this) { //Binding function to this
if (typeof this[key] === "function") this[key] = (<any>this[key]).bind(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) { addAdapter(adapter: Adapter) {
this.addAdapter(new ConsoleAdapter()); 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<void>;
flush(sync: boolean): void | Promise<void> {
if (sync) {
this.adapter.forEach((elm) => elm.flush(true));
} else {
let adapters: (void | Promise<void>)[] = [];
this.adapter.forEach((elm) => adapters.push(elm.flush(false)));
return Promise.all(adapters).then(() => {});
}
}
//Binding function to this public close() {
this.debug = this.debug.bind(this); this.adapter.forEach((adapter) =>
this.log = this.log.bind(this); adapter.close ? adapter.close() : undefined
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);
}
addAdapter(adapter: Adapter) { public waitForSetup() {
if (!this.adapter.has(adapter)) { return Promise.all(this.adapter_init);
this.adapter.add(adapter); }
let prms = Promise.resolve(adapter.init(this.messageObservable.getPublicApi(), this._name));
this.adapter_init.push(prms);
}
}
flush(sync: true): void; debug(...message: any[]) {
flush(sync: false): Promise<void>; if (this._logLevel <= LoggingTypes.Debug)
flush(sync: boolean): void | Promise<void> { this.message(LoggingTypes.Debug, message);
if (sync) { }
this.adapter.forEach(elm => elm.flush(true));
} else {
let adapters: (void | Promise<void>)[] = [];
this.adapter.forEach(elm => adapters.push(elm.flush(false)));
return Promise.all(adapters).then(() => { });
}
}
public close() { log(...message: any[]) {
this.adapter.forEach(adapter => adapter.close ? adapter.close() : undefined); if (this._logLevel <= LoggingTypes.Log)
} this.message(LoggingTypes.Log, message);
}
public waitForSetup() { warning(...message: any[]) {
return Promise.all(this.adapter_init); if (this._logLevel <= LoggingTypes.Warning)
} this.message(LoggingTypes.Warning, message);
}
debug(...message: any[]) { warn(...message: any[]) {
if (this._logLevel <= LoggingTypes.Debug) if (this._logLevel <= LoggingTypes.Warning)
this.message(LoggingTypes.Debug, message); this.message(LoggingTypes.Warning, message);
} }
log(...message: any[]) { error(error: Error | string) {
if (this._logLevel <= LoggingTypes.Log) if (this._logLevel > LoggingTypes.Error) return;
this.message(LoggingTypes.Log, message); 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[]) { errorMessage(...message: any[]) {
if (this._logLevel <= LoggingTypes.Warning) if (this._logLevel <= LoggingTypes.Error)
this.message(LoggingTypes.Warning, message); this.message(LoggingTypes.Error, message);
} }
warn(...message: any[]) { protected getCurrentTime() {
if (this._logLevel <= LoggingTypes.Warning) if (browser && window.performance && window.performance.now) {
this.message(LoggingTypes.Warning, message); return window.performance.now();
} } else {
return Date.now();
}
}
error(error: Error | string) { /**
if (this._logLevel > LoggingTypes.Error) * The time difference in milliseconds (fractions allowed!)
return; * @param start Start time from getCurrentTime
if (!error) error = "Empty ERROR was passed, so no informations available"; */
if (typeof error === "string") { protected getTimeDiff(start: any) {
let e = new Error("This is a fake error, to get a stack trace"); if (browser && window.performance && window.performance.now) {
this.message(LoggingTypes.Error, [error, "\n", e.stack]); return window.performance.now() - start;
} else { } else {
this.message(LoggingTypes.Error, [error.message, "\n", error.stack], getCallerFromExisting(error)); return Date.now() - start;
} }
} }
errorMessage(...message: any[]) { time(id?: string, name = id) {
if (this._logLevel <= LoggingTypes.Error) if (!id) {
this.message(LoggingTypes.Error, message); id = Math.floor(Math.random() * 899999 + 100000).toString();
} }
private message(type: LoggingTypes, message: any[], caller?: { file: string, line: number }) { this.timerMap.set(id, {
let date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); name,
start: this.getCurrentTime(),
});
let file_raw = caller || getCallerFile(); return {
let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`; id,
end: () => this.timeEnd(id),
};
}
let type_str = LoggingTypes[type].toUpperCase().padEnd(5, " "); timeEnd(id: string) {
let type_format: Format[] = []; let timer = this.timerMap.get(id);
switch (type) { if (timer) {
case LoggingTypes.Log: let diff = this.getTimeDiff(timer.start);
type_format = this._formatMap.log; Logging.message(LoggingTypes.Debug, [
break; withColor(Colors.GREEN, `[${timer.name}]`),
case LoggingTypes.Error: `->`,
type_format = this._formatMap.error; withColor(Colors.BLUE, diff.toFixed(4)),
break; "ms",
case LoggingTypes.Debug: ]);
type_format = this._formatMap.debug; }
break; }
case LoggingTypes.Warning:
type_format = this._formatMap.warning;
break;
}
const prefix: FormattedText[] = []; private message(
const a = (text: string, formats: Format[] = []) => { type: LoggingTypes,
prefix.push({ message: any[],
text, caller?: { file: string; line: number }
formats ) {
}) let date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "");
}
a("["); let file_raw = caller || getCallerFile();
a(date, this._formatMap.date); let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`;
a("][");
a(type_str, type_format);
if (file_raw.file) {
a("][");
a(file, this._formatMap.file);
}
a("]: ");
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[] = []; a("[");
let line: FormattedLine; a(date, this._formatMap.date);
const newLine = () => { a("][");
if (line && line.length > 0) { a(type_str, type_format);
formatted.push(line); if (file_raw.file) {
raw.push(line.map(e => e.text).join("")); 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]; if (typeof e !== "string")
} e = inspect(e, {
newLine(); colors: true,
showHidden: true,
depth: 3,
}) as string;
}
message.forEach((e, i) => { removeColors(e)
let formats: Format[] = []; .split("\n")
if (typeof e !== "string") { .map((text, index, { length }) => {
if (e && typeof e === "object") { line.push({ text, formats });
if (e[colorSymbol]) { if (index < length - 1) {
formats.push({ newLine();
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 }) => { if (!e.endsWith("\n") && i < message.length - 1) {
line.push({ text, formats }); line.push({ text: " ", formats: [] });
if (index < length - 1) { }
newLine(); });
}
})
if (!e.endsWith("\n") && i < message.length - 1) { newLine();
line.push({ text: " ", formats: [] });
}
});
newLine(); let msg: Message = {
date: new Date(),
file,
name: this._name,
text: {
raw,
formatted,
},
type,
};
let msg: Message = { this.messageObservable.send(msg);
date: new Date(), }
file,
name: this._name,
text: {
raw,
formatted
},
type
}
this.messageObservable.send(msg);
}
} }
const colorSymbol = Symbol("color"); const colorSymbol = Symbol("color");
export interface ColorFormat { export interface ColorFormat {
[colorSymbol]: Colors; [colorSymbol]: Colors;
value: any; value: any;
} }
export function withColor(color: Colors, value: any): ColorFormat { export function withColor(color: Colors, value: any): ColorFormat {
return { return {
[colorSymbol]: color, [colorSymbol]: color,
value value,
} };
} }
function getStack() { function getStack() {
// Save original Error.prepareStackTrace // Save original Error.prepareStackTrace
let origPrepareStackTrace = (<any>Error).prepareStackTrace; let origPrepareStackTrace = (<any>Error).prepareStackTrace;
// Override with function that just returns `stack` // Override with function that just returns `stack`
(<any>Error).prepareStackTrace = function (_, stack) { (<any>Error).prepareStackTrace = function (_, stack) {
return stack return stack;
} };
// Create a new `Error`, which automatically gets `stack` // Create a new `Error`, which automatically gets `stack`
let err = new Error(); let err = new Error();
// Evaluate `err.stack`, which calls our new `Error.prepareStackTrace` // Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
let stack: any[] = <any>err.stack; let stack: any[] = <any>err.stack;
// Restore original `Error.prepareStackTrace` // Restore original `Error.prepareStackTrace`
(<any>Error).prepareStackTrace = origPrepareStackTrace; (<any>Error).prepareStackTrace = origPrepareStackTrace;
// Remove superfluous function call on stack // Remove superfluous function call on stack
stack.shift(); // getStack --> Error stack.shift(); // getStack --> Error
return stack return stack;
} }
function baseName(path) { function baseName(path) {
return path.split(/[\\/]/).pop(); return path.split(/[\\/]/).pop();
} }
function getCallerFile() { function getCallerFile() {
try { try {
let stack = getStack() let stack = getStack();
let current_file = stack.shift().getFileName(); let current_file = stack.shift().getFileName();
while (stack.length) { while (stack.length) {
let caller_file = stack.shift(); let caller_file = stack.shift();
if (current_file !== caller_file.getFileName()) 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 { return {
file: f, line: Number(line) 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),
};
}
}
} }

View File

@ -1,6 +1,12 @@
import { ObservableInterface } from "@hibas123/utils"; import { ObservableInterface } from "@hibas123/utils";
import { Colors } from "./index"; 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"; const browser = typeof window !== "undefined";
@ -9,7 +15,7 @@ export class ConsoleAdapter implements Adapter {
observable.subscribe(this.onMessage.bind(this)); observable.subscribe(this.onMessage.bind(this));
} }
flush() { } flush() {}
// TODO: Check if required! // TODO: Check if required!
// private escape(text: string): string { // private escape(text: string): string {
@ -128,20 +134,21 @@ export class ConsoleAdapter implements Adapter {
if (message.name) prefix = `[${message.name}]=>`; if (message.name) prefix = `[${message.name}]=>`;
if (browser) { if (browser) {
let formats: string[] = []; let formats: string[] = [];
let text = lines.map(line => { let text = lines
let [t, fmts] = this.formatLine(line); .map((line) => {
formats.push(...fmts); let [t, fmts] = this.formatLine(line);
return t; formats.push(...fmts);
}).join("\n"); return t;
})
.join("\n");
// console.log(formats); // console.log(formats);
console.log(text, ...formats); console.log(text, ...formats);
} else { } else {
lines.forEach(line => { lines.forEach((line) => {
let [text] = this.formatLine(line); let [text] = this.formatLine(line);
console.log(prefix + text); console.log(prefix + text);
}) });
} }
} }
} }

View File

@ -1,39 +1,44 @@
import { Logging, LoggingBase, LoggingTypes, Colors, withColor } from "."; import { Logging, LoggingBase, LoggingTypes, Colors, withColor } from ".";
Logging.log("test") Logging.log("test");
Logging.log("i", "am", { a: "an" }, 1000); Logging.log("i", "am", { a: "an" }, 1000);
Logging.error(new Error("fehler 001")); Logging.error(new Error("fehler 001"));
Logging.debug("Some Debug infos"); Logging.debug("Some Debug infos");
Logging.errorMessage("i", "am", "an", "error"); 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(
Logging.log(withColor(Colors.MAGENTA, { somekey: "Some value" })) withColor(Colors.MAGENTA, "This text should be magenta!"),
"This not!"
);
Logging.log(withColor(Colors.MAGENTA, { somekey: "Some value" }));
let err = new Error() let err = new Error();
if (typeof err.stack !== "string") console.log("Stacktrace invalid", err.stack) if (typeof err.stack !== "string") console.log("Stacktrace invalid", err.stack);
let cus = new LoggingBase({ name: "test" }); 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!"); cus.log("This has some %c symbols inside of it!");
let cus2 = new LoggingBase("test2"); let cus2 = new LoggingBase("test2");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
let cus22 = new LoggingBase("test2"); let cus22 = new LoggingBase("test2");
cus22.log("Hello from custom Logger 22") cus22.log("Hello from custom Logger 22");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
cus22.log("Hello from custom Logger 22") cus22.log("Hello from custom Logger 22");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
cus22.log("Hello from custom Logger 22") cus22.log("Hello from custom Logger 22");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
cus22.log("Hello from custom Logger 22") cus22.log("Hello from custom Logger 22");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
cus22.log("Hello from custom Logger 22") cus22.log("Hello from custom Logger 22");
cus2.log("Hello from custom Logger 2") cus2.log("Hello from custom Logger 2");
Logging.debug("Only Errors should appear:") Logging.debug("Only Errors should appear:");
Logging.logLevel = LoggingTypes.Error; Logging.logLevel = LoggingTypes.Error;
Logging.debug("This should not be there 1"); Logging.debug("This should not be there 1");
@ -43,3 +48,6 @@ Logging.warning("This should not be there 4");
Logging.error("This should be there 1"); Logging.error("This should be there 1");
Logging.errorMessage("This should be there 2"); Logging.errorMessage("This should be there 2");
const timer = Logging.time("timer1", "Test Timer");
setTimeout(() => timer.end(), 1000);