This commit is contained in:
Fabian Stamm 2021-05-08 21:47:00 +02:00
parent 176d37249d
commit 96d7808f35
11 changed files with 3229 additions and 832 deletions

2
.vscode/launch.json vendored
View File

@ -9,7 +9,7 @@
"request": "launch", "request": "launch",
"name": "Launch Program", "name": "Launch Program",
"skipFiles": ["<node_internals>/**"], "skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\out\\test.js", "program": "${workspaceFolder}/out/test.js",
"preLaunchTask": "tsc: build - tsconfig.json", "preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/out/**/*.js"] "outFiles": ["${workspaceFolder}/out/**/*.js"]
} }

View File

@ -1,29 +1,29 @@
# Logging
Simple logging module, that supports terminal coloring and different plugins Simple logging module, that supports terminal coloring and different plugins
# Getting Started ## Getting Started
```javascript ```javascript
const Logging = require("@hibas123/logging").default;
const Logging = require("@hibas123/logging").Logging;
Logging.log("Hello there"); Logging.log("Hello there");
``` ```
There are different Logging levels, that also apply terminal coloring: There are different Logging levels, that also apply terminal coloring:
```javascript ```javascript
Logging.debug("Debug message") Logging.debug("Debug message");
Logging.log("Log message") Logging.log("Log message");
Logging.warning("Warning") Logging.warning("Warning");
Logging.error(new Error("To less creativity")) Logging.error(new Error("To less creativity"));
Logging.error("Just an simple message as error") Logging.error("Just an simple message as error");
Logging.errorMessage("Nearly the same as error") Logging.errorMessage("Nearly the same as error");
``` ```
All Logging types except the simple error take as many arguments as you want. These will be joined with spaces and serialized with the node util.inspect function. All Logging types except the simple error take as many arguments as you want. These will be joined with spaces and serialized with the node util.inspect function.
# Setup ## Setup
This logging module doesn't require any setup per default, but sometimes it makes sense to configure a few things. This logging module doesn't require any setup per default, but sometimes it makes sense to configure a few things.
@ -32,14 +32,16 @@ For example can you disable the console output. This may be helpful, if you inse
Also you can set a name. All messages that are send with this instance are prefixed by this name. Also you can set a name. All messages that are send with this instance are prefixed by this name.
```javascript ```javascript
const CustomLogging = new LoggingBase(name | { const CustomLogging = new LoggingBase(
name |
{
name: "custom", // default undefined name: "custom", // default undefined
console: false // default true console: false, // default true
}); }
);
``` ```
## Plugins
# Plugins
There is a Plugin API available, that makes is possible to add custom Logging Adapter. There is a Plugin API available, that makes is possible to add custom Logging Adapter.
@ -52,7 +54,9 @@ The adapters need to provide a very simple Interface:
```typescript ```typescript
interface Adapter { interface Adapter {
init(observable: ObservableInterface<Message>, name?: string): void | Promise<void>; init(): void | Promise<void>;
onMessage(message: Message): void;
flush(sync: true): void; flush(sync: true): void;
flush(sync: false): void | Promise<void>; flush(sync: false): void | Promise<void>;
@ -60,28 +64,33 @@ interface Adapter {
interface Message { interface Message {
type: LoggingTypes; type: LoggingTypes;
name?:string; names?: string[];
text: { text: IFormatted;
raw: string[],
formatted: string[]
};
date: Date; date: Date;
file: string; file: string;
} }
export interface IFormatted {
color?: IColors;
bgcolor?: IColors;
bold?: boolean;
italic?: boolean;
blink?: boolean;
underscore?: boolean;
content: string;
}
enum LoggingTypes { enum LoggingTypes {
Log, Log,
Warning, Warning,
Error, Error,
Debug Debug,
} }
``` ```
The `ObservableInterface` comes from `@hibas123/utils`. It provides a very simple api for subscribing and unsubscribing from the message events. ## License
More Details on Observable [git](https://git.stamm.me/OpenServer/Utils) or [npm](https://www.npmjs.com/package/@hibas123/utils)
# License
MIT MIT
Copyright (c) 2018 Fabian Stamm Copyright (c) 2018 Fabian Stamm

8
deno_build.js Normal file
View File

@ -0,0 +1,8 @@
const pkg = JSON.parse(await Deno.readTextFile("package.json"));
const meta = JSON.parse(await Deno.readTextFile("meta.json"));
meta.version = pkg.version;
await Deno.copyFile("README.md", "esm/README.md");
await Deno.writeTextFile("meta.json", JSON.stringify(meta, undefined, 3));

16
meta.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "logging",
"version": "3.0.0",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"hooks": {
"prepublish": "deno_build.js"
},
"root": "esm",
"files": [
"esm/**/*.ts",
"esm/**/*.js",
"esm/README.md"
]
}

2810
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@hibas123/logging", "name": "@hibas123/logging",
"version": "2.6.1", "version": "3.0.0",
"description": "", "description": "",
"main": "out/index.js", "main": "out/index.js",
"types": "out/index.d.ts", "types": "out/index.d.ts",
@ -8,7 +8,8 @@
"scripts": { "scripts": {
"prepublish": "npm run build", "prepublish": "npm run build",
"build": "tsc && tsc -p tsconfig.esm.json", "build": "tsc && tsc -p tsconfig.esm.json",
"dev": "nodemon -e ts --exec ts-node src/test.ts" "test": "tsc && node out/test.js",
"postpublish": "denreg publish"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -24,12 +25,10 @@
"readme.md" "readme.md"
], ],
"devDependencies": { "devDependencies": {
"concurrently": "^5.3.0", "concurrently": "^6.0.2",
"nodemon": "^2.0.4", "nodemon": "^2.0.7",
"ts-node": "^9.0.0", "ts-node": "^9.1.1",
"typescript": "^4.0.3" "typescript": "^4.2.4"
}, },
"dependencies": { "dependencies": {}
"@hibas123/utils": "^2.2.10"
}
} }

View File

@ -1,36 +1,34 @@
import { Observable, ObservableInterface } from "@hibas123/utils";
import { ConsoleAdapter } from "./consolewriter.js"; import { ConsoleAdapter } from "./consolewriter.js";
import inspect from "./inspect.js"; import inspect from "./inspect.js";
import { import {
Adapter, Adapter,
LoggingTypes,
Message,
FormatConfig,
DefaultFormatConfig, DefaultFormatConfig,
Format, Format,
FormatTypes, FormatConfig,
Colors, Formatted,
FormattedText, LoggingTypes,
FormattedLine, Message,
} from "./types.js"; } from "./types.js";
const browser = typeof window !== "undefined"; const browser = typeof window !== "undefined";
export function removeColors(text: string) { export interface ILoggingTimer {
text = text.replace( end: () => number;
/[\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;
} }
export interface LoggingBaseOptions { export interface ILoggingInterface {
debug(...message: any[]): void;
log(...message: any[]): void;
warn(...message: any[]): void;
error(...message: any[]): void;
time(name?: string): ILoggingTimer;
timeEnd(id: string): number;
getChild(name: string): ILoggingInterface;
}
export interface ILoggingOptions {
/** /**
* 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
*/ */
@ -46,267 +44,97 @@ export interface LoggingBaseOptions {
resolve_filename: boolean; resolve_filename: boolean;
} }
const adapterCache = new WeakMap<Adapter, number>(); export interface INativeFunctions {
startTimer(): any;
endTimer(start: any): number;
}
class AdapterSet { export const DefaultNativeFunctions = {
change = new Observable<{ type: "add" | "remove"; adapter: Adapter }>(); startTimer: () => {
adapters: Set<Adapter> = new Set(); if (browser && window.performance && window.performance.now) {
return window.performance.now();
addAdapter(adapter: Adapter) { } else {
if (!this.adapters.has(adapter)) { return Date.now();
this.adapters.add(adapter);
this.change.send({
type: "add",
adapter: adapter,
});
}
} }
},
endTimer: (start: any) => {
if (browser && window.performance && window.performance.now) {
return window.performance.now() - start;
} else {
return Date.now() - start;
} }
},
} as INativeFunctions;
const consoleAdapter = new ConsoleAdapter(); const consoleAdapter = new ConsoleAdapter();
declare var process: { cwd: () => string }; declare var process: { cwd: () => string };
const PROJECT_ROOT = typeof process !== "undefined" ? process.cwd() : undefined; const PROJECT_ROOT = typeof process !== "undefined" ? process.cwd() : undefined;
export class LoggingBase { const InitialisedAdapters = Symbol("@hibas123/logging:initialisedAdapters");
private _formatMap: FormatConfig = new DefaultFormatConfig();
public set formatMap(value: FormatConfig) { export abstract class LoggingInterface implements ILoggingInterface {
this._formatMap = value; #names: string[];
#timerMap = new Map<string, { name: string; start: any }>();
get names() {
return [...this.#names];
} }
private adapterSet: AdapterSet; protected abstract message(
private adapter_init: Promise<void>[] = []; type: LoggingTypes,
names: string[],
message: any[],
caller?: { file: string; line: number; column?: number }
): void;
private timerMap = new Map<string, { name: string; start: any }>(); constructor(names: string[]) {
this.#names = names;
private messageObservable = new Observable<Message>();
protected _name: string;
public resolve_filename: boolean;
private _logLevel = LoggingTypes.Debug;
get logLevel() {
return this._logLevel;
}
set logLevel(value: LoggingTypes) {
this._logLevel = value;
}
get name() {
return this._name;
}
constructor(
options?: Partial<LoggingBaseOptions> | string,
adapterSet?: AdapterSet
) {
let opt: Partial<LoggingBaseOptions>;
if (!options) opt = {};
else if (typeof options === "string") {
opt = { name: options };
} else {
opt = options;
}
let config: LoggingBaseOptions = {
name: undefined,
console: true,
resolve_filename: true,
...opt,
};
if (config.name) this._name = config.name;
this.resolve_filename = config.resolve_filename;
for (let key in this) {
if (typeof this[key] === "function")
this[key] = (<any>this[key]).bind(this);
}
if (adapterSet) {
this.adapterSet = adapterSet;
this.adapterSet.adapters.forEach((a) => this.initAdapter(a));
} else {
this.adapterSet = new AdapterSet();
}
this.adapterSet.change.subscribe((change) => {
if (change.type === "add") {
this.initAdapter(change.adapter);
}
});
if (config.console) {
this.addAdapter(consoleAdapter);
}
//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);
}
/**
* Can be used to override function from super class
* @param child New child logging instance
*/
protected postGetChild(child: LoggingBase) {}
/**
* Creates a new logging instance, with the adapters liked together.
* @param name Name/Prefix of the new child. The actual name will resolve as "<parent-name>/<name>"
*/
getChild(name: string) {
let lg = new LoggingBase(
{
console: false,
name: this.name ? this.name + "/" + name : name,
},
this.adapterSet
);
return lg;
}
private initAdapter(adapter: Adapter) {
let cached = adapterCache.get(adapter) || 0;
adapterCache.set(adapter, cached + 1);
let prms = Promise.resolve(
adapter.init(this.messageObservable.getPublicApi())
);
this.adapter_init.push(prms);
}
addAdapter(adapter: Adapter) {
this.adapterSet.addAdapter(adapter);
}
flush(sync: true): void;
flush(sync: false): Promise<void>;
flush(sync: boolean): void | Promise<void> {
if (sync) {
this.adapterSet.adapters.forEach((elm) => elm.flush(true));
} else {
let adapters: (void | Promise<void>)[] = [];
this.adapterSet.adapters.forEach((elm) =>
adapters.push(elm.flush(false))
);
return Promise.all(adapters).then(() => {});
}
}
#closed = false;
public close() {
if (this.#closed) return;
this.#closed = true;
this.adapterSet.adapters.forEach((adapter) => {
let cached = adapterCache.get(adapter);
if (cached) {
cached--;
if (cached <= 0) {
adapterCache.delete(adapter);
adapter.close();
} else adapterCache.set(adapter, cached);
}
adapter.close ? adapter.close() : undefined;
});
this.adapterSet = undefined;
this.messageObservable.close();
}
public waitForSetup() {
return Promise.all(this.adapter_init);
} }
debug(...message: any[]) { debug(...message: any[]) {
if (this._logLevel <= LoggingTypes.Debug) this.message(LoggingTypes.Debug, this.#names, message);
this.message(LoggingTypes.Debug, message);
} }
log(...message: any[]) { log(...message: any[]) {
if (this._logLevel <= LoggingTypes.Log) this.message(LoggingTypes.Log, this.#names, message);
this.message(LoggingTypes.Log, message);
} }
warning(...message: any[]) { warning(...message: any[]) {
if (this._logLevel <= LoggingTypes.Warning) this.message(LoggingTypes.Warn, this.#names, message);
this.message(LoggingTypes.Warning, message);
} }
warn(...message: any[]) { warn(...message: any[]) {
if (this._logLevel <= LoggingTypes.Warning) this.message(LoggingTypes.Warn, this.#names, message);
this.message(LoggingTypes.Warning, message);
} }
error(error: Error | string, ...message: any[]) { error(error: Error | string, ...message: any[]) {
if (this._logLevel > LoggingTypes.Error) return;
if (!error) if (!error)
error = "Empty ERROR was passed, so no informations available"; error = "Empty ERROR was passed, so no informations available";
if (typeof error === "string") { if (typeof error === "string") {
let e = new Error("This is a fake error, to get a stack trace"); let e = new Error("This is a fake error, to get a stack trace");
this.message(LoggingTypes.Error, [ this.message(LoggingTypes.Error, this.#names, [
error, error,
...message,
"\n", "\n",
e.stack, e.stack,
"\n", "\n",
...message,
]); ]);
} else { } else {
this.message( this.message(
LoggingTypes.Error, LoggingTypes.Error,
this.#names,
[error.message, "\n", error.stack, "\n", ...message], [error.message, "\n", error.stack, "\n", ...message],
getCallerFromExisting(error) getCallerFromExisting(error)
); );
} }
} }
errorMessage(...message: any[]) { time(id?: string) {
if (this._logLevel <= LoggingTypes.Error) if (!id) id = Math.floor(Math.random() * 899999 + 100000).toString();
this.message(LoggingTypes.Error, message);
}
protected getCurrentTime(): any { this.#timerMap.set(id, {
if (browser && window.performance && window.performance.now) { name: id,
return window.performance.now(); start: Logging.nativeFunctions.startTimer(),
} else {
return Date.now();
}
}
/**
* 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;
}
}
time(id?: string, name = id) {
if (!id) {
id = Math.floor(Math.random() * 899999 + 100000).toString();
}
this.timerMap.set(id, {
name,
start: this.getCurrentTime(),
}); });
return { return {
@ -316,31 +144,121 @@ export class LoggingBase {
} }
timeEnd(id: string) { timeEnd(id: string) {
let timer = this.timerMap.get(id); let timer = this.#timerMap.get(id);
if (timer) { if (timer) {
let diff = this.getTimeDiff(timer.start); let diff = Logging.nativeFunctions.endTimer(timer.start);
this.message(LoggingTypes.Debug, [
withColor(Colors.GREEN, `[${timer.name}]`), this.message(LoggingTypes.Debug, this.#names, [
Format.green(`[${timer.name}]`),
`->`, `->`,
withColor(Colors.BLUE, diff.toFixed(4)), Format.blue(diff.toFixed(4)),
"ms", "ms",
]); ]);
return diff; return diff;
} }
return -1; return -1;
} }
private message( abstract getChild(name: string): ILoggingInterface;
}
export class Logging extends LoggingInterface {
private static [InitialisedAdapters] = new Map<Adapter, number>();
public static nativeFunctions: INativeFunctions = DefaultNativeFunctions;
static DecoupledLogging = class extends LoggingInterface {
#lg: Logging;
constructor(names: string[], lg: Logging) {
super(names);
this.#lg = lg;
}
message(...params: [any, any, any, any]) {
this.#lg.message(...params);
}
getChild(name: string) {
return new Logging.DecoupledLogging([this.names, name], this.#lg);
}
};
#closed = false;
#logLevel = LoggingTypes.Debug;
#resolve_filename: boolean;
#format_map: FormatConfig = new DefaultFormatConfig();
#adapters = new Set<Adapter>();
set logLevel(level: LoggingTypes) {
this.#logLevel = level;
}
get logLevel() {
return this.#logLevel;
}
constructor(options?: Partial<ILoggingOptions>) {
super(options?.name ? [options.name] : []);
options = {
console: true,
resolve_filename: true,
...options,
};
if (options.console) {
this.addAdapter(consoleAdapter);
}
for (const key in this) {
if (typeof this[key] === "function") {
this[key] = ((this[key] as never) as Function).bind(this);
}
}
}
async addAdapter(adapter: Adapter) {
const init = adapter.init();
const add = () => {
this.#adapters.add(adapter);
if (Logging[InitialisedAdapters].has(adapter)) {
Logging[InitialisedAdapters].set(
adapter,
Logging[InitialisedAdapters].get(adapter) + 1
);
} else {
Logging[InitialisedAdapters].set(adapter, 1);
}
};
if (!init) {
add();
} else {
Promise.resolve(init).then(add);
}
}
getChild(name: string): ILoggingInterface {
return new Logging.DecoupledLogging([...this.names, name], this);
}
protected message(
type: LoggingTypes, type: LoggingTypes,
names: string[],
message: any[], message: any[],
caller?: { file: string; line: number; column?: number } caller?: { file: string; line: number; column?: number }
) { ) {
if (this.#closed) return; if (this.#logLevel > type) return;
if (this.#closed) {
//TODO: Maybe error?
return;
}
let date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, ""); const date = new Date();
const date_str = date.toISOString().replace(/T/, " ").replace(/\..+/, "");
let file: string | undefined = undefined; let file: string | undefined = undefined;
if (this.resolve_filename) { if (this.#resolve_filename) {
let file_raw = caller; let file_raw = caller;
if (!file_raw) { if (!file_raw) {
try { try {
@ -372,116 +290,118 @@ export class LoggingBase {
} }
let type_str = LoggingTypes[type].toUpperCase().padEnd(5, " "); let type_str = LoggingTypes[type].toUpperCase().padEnd(5, " ");
let type_format: Format[] = []; let type_format: Formatted;
switch (type) { switch (type) {
case LoggingTypes.Log: case LoggingTypes.Log:
type_format = this._formatMap.log; type_format = this.#format_map.log;
break; break;
case LoggingTypes.Error: case LoggingTypes.Error:
type_format = this._formatMap.error; type_format = this.#format_map.error;
break; break;
case LoggingTypes.Debug: case LoggingTypes.Debug:
type_format = this._formatMap.debug; type_format = this.#format_map.debug;
break; break;
case LoggingTypes.Warning: case LoggingTypes.Warn:
type_format = this._formatMap.warning; type_format = this.#format_map.warning;
break; break;
} }
const prefix: FormattedText[] = []; const nameFormatted: Formatted<string>[] = [];
const a = (text: string, formats: Format[] = []) => { if (names.length > 0) {
prefix.push({ nameFormatted.push(new Formatted("["));
text, for (let i = 0; i < names.length; i++) {
formats, nameFormatted.push(new Formatted(names[i], this.#format_map.names));
}); if (i < names.length - 1) {
}; nameFormatted.push(this.#format_map.names_delimiter);
a("[");
a(date, this._formatMap.date);
a("][");
a(type_str, type_format);
if (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;
} }
} }
if (typeof e !== "string") nameFormatted.push(new Formatted("]"));
e = inspect(e, { }
colors: true,
let linePrefix = [
new Formatted("["),
new Formatted(date_str, this.#format_map.date),
new Formatted("]["),
new Formatted(type_str, type_format),
...(file
? [new Formatted("]["), new Formatted(file, this.#format_map.file)]
: []),
new Formatted("]"),
...nameFormatted,
new Formatted(": "),
];
// let linePrefix = [
// ...namePrefix,
// new Formatted(date_str, this.#format_map.date),
// new Formatted(" "),
// new Formatted(type_str, type_format),
// ...(file
// ? [new Formatted(" "), new Formatted(file, this.#format_map.file)]
// : []),
// new Formatted("| "),
// ];
let formattedMessage: Formatted<string>[] = [...linePrefix];
message.forEach((msg, idx) => {
let format = new Formatted();
if (msg instanceof Formatted) {
format = msg;
msg = msg.content;
}
if (typeof msg !== "string") {
msg = inspect(msg, {
colors: false, //TODO: Maybe change when changing the removeColors to return formatted text?
showHidden: true, showHidden: true,
depth: 3, depth: 3,
}) as string; }) as string;
} }
removeColors(e) removeColors(msg)
.split("\n") .split("\n")
.map((text, index, { length }) => { .forEach((text, index, { length }) => {
line.push({ text, formats }); if (index != length - 1) {
if (index < length - 1) { formattedMessage.push(
newLine(); new Formatted(text + "\n", format),
...linePrefix
);
} else {
formattedMessage.push(new Formatted(text, format));
} }
}); });
formattedMessage.push(new Formatted(" "));
if (!e.endsWith("\n") && i < message.length - 1) {
line.push({ text: " ", formats: [] });
}
}); });
newLine();
let msg: Message = { let msg: Message = {
date: new Date(), date,
file, file,
name: this._name, names,
text: { text: formattedMessage,
raw,
formatted,
},
type, type,
}; };
this.messageObservable.send(msg); this.#adapters.forEach((adapter) => adapter.onMessage(msg));
}
} }
const colorSymbol = Symbol("color"); async close() {
if (this.#closed) return;
this.#closed = true;
export interface ColorFormat { this.#adapters.forEach((adapter) => {
[colorSymbol]: Colors; const cnt = Logging[InitialisedAdapters].get(adapter);
value: any; if (!cnt) {
//TODO: should not happen!
} else {
if (cnt <= 1) {
adapter.close();
Logging[InitialisedAdapters].delete(adapter);
} else {
Logging[InitialisedAdapters].set(adapter, cnt - 1);
}
}
});
} }
export function withColor(color: Colors, value: any): ColorFormat {
return {
[colorSymbol]: color,
value,
};
} }
function getStack() { function getStack() {
@ -556,3 +476,12 @@ function getCallerFromExisting(
} }
} }
} }
export function removeColors(text: string) {
text = text.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
""
);
return text;
}

View File

@ -1,24 +1,23 @@
import { ObservableInterface } from "@hibas123/utils";
import { Colors } from "./index.js"; import { Colors } from "./index.js";
import { import {
Adapter, Adapter,
Message, Message,
FormattedLine,
TerminalFormats, TerminalFormats,
FormatTypes, Formatted,
IFormatted,
} from "./types.js"; } from "./types.js";
declare const Deno: any; declare const Deno: any;
declare const process: any;
const browser = typeof window !== "undefined" && typeof Deno === "undefined"; const browser = typeof window !== "undefined" && typeof Deno === "undefined";
const deno = typeof Deno !== "undefined";
const NodeJS = typeof process !== undefined;
export class ConsoleAdapter implements Adapter { export class ConsoleAdapter implements Adapter {
constructor(private colors: boolean = true) {} constructor(private colors: boolean = true) {}
init(observable: ObservableInterface<Message>) { init() {}
observable.subscribe(this.onMessage.bind(this));
}
flush() {} flush() {}
// TODO: Check if required! // TODO: Check if required!
@ -28,25 +27,29 @@ export class ConsoleAdapter implements Adapter {
// .replace(/%c/g, "%%c") // .replace(/%c/g, "%%c")
// } // }
private formatLine(line: FormattedLine): [string, string[] | undefined] { private format(
formatted: IFormatted<string>[]
): [string, string[] | undefined] {
let text = ""; let text = "";
let style_formats: string[] = []; let style_formats: string[] = [];
if (!browser) { if (!browser) {
for (let part of line) { // NodeJS or Deno
for (const format of formatted) {
let formats = ""; let formats = "";
for (let format of part.formats) { if (format.bold) {
switch (format.type) {
case FormatTypes.BOLD:
formats += TerminalFormats.Bold; formats += TerminalFormats.Bold;
break; }
case FormatTypes.UNDERSCORE:
formats += TerminalFormats.Underscore; if (format.blink) {
break;
case FormatTypes.BLINK:
formats += TerminalFormats.Blink; formats += TerminalFormats.Blink;
break; }
case FormatTypes.COLOR:
if (format.underscore) {
formats += TerminalFormats.Underscore;
}
if (format.color) {
switch (format.color) { switch (format.color) {
case Colors.RED: case Colors.RED:
formats += TerminalFormats.FgRed; formats += TerminalFormats.FgRed;
@ -70,30 +73,57 @@ export class ConsoleAdapter implements Adapter {
formats += TerminalFormats.FgWhite; formats += TerminalFormats.FgWhite;
break; break;
} }
}
if (format.bgcolor) {
switch (format.bgcolor) {
case Colors.RED:
formats += TerminalFormats.BgRed;
break;
case Colors.GREEN:
formats += TerminalFormats.BgGreen;
break;
case Colors.YELLOW:
formats += TerminalFormats.BgYellow;
break;
case Colors.BLUE:
formats += TerminalFormats.BgBlue;
break;
case Colors.MAGENTA:
formats += TerminalFormats.BgMagenta;
break;
case Colors.CYAN:
formats += TerminalFormats.BgCyan;
break;
case Colors.WHITE:
formats += TerminalFormats.BgWhite;
break; break;
} }
} }
text += formats + part.text + TerminalFormats.Reset;
text += formats + format.content + TerminalFormats.Reset;
// (formats.length > 0 ? TerminalFormats.Reset : ""); //TODO: Benchmark if this is better
} }
} else { } else {
for (let part of line) { for (const format of formatted) {
let styles: string[] = []; let styles: string[] = [];
let resetStyles: string[] = []; let resetStyles: string[] = [];
for (let format of part.formats) { if (format.bold) {
switch (format.type) {
case FormatTypes.BOLD:
styles.push("font-weight: bold;"); styles.push("font-weight: bold;");
resetStyles.push("font-weight: unset"); resetStyles.push("font-weight: unset");
break; }
case FormatTypes.UNDERSCORE:
if (format.underscore) {
styles.push("text-decoration: underline"); styles.push("text-decoration: underline");
resetStyles.push("text-decoration: unset"); resetStyles.push("text-decoration: unset");
break; }
case FormatTypes.BLINK:
if (format.blink) {
styles.push("text-decoration: blink"); styles.push("text-decoration: blink");
resetStyles.push("text-decoration: unset"); resetStyles.push("text-decoration: unset");
break; }
case FormatTypes.COLOR:
if (format.color) {
let color = ""; let color = "";
switch (format.color) { switch (format.color) {
case Colors.RED: case Colors.RED:
@ -120,10 +150,38 @@ export class ConsoleAdapter implements Adapter {
} }
styles.push("color: " + color); styles.push("color: " + color);
resetStyles.push("color: unset"); resetStyles.push("color: unset");
}
if (format.bgcolor) {
let color = "";
switch (format.bgcolor) {
case Colors.RED:
color = "red";
break;
case Colors.GREEN:
color = "green";
break;
case Colors.YELLOW:
color = "gold";
break;
case Colors.BLUE:
color = "blue";
break;
case Colors.MAGENTA:
color = "magenta";
break;
case Colors.CYAN:
color = "cyan";
break;
case Colors.WHITE:
color = "white";
break; break;
} }
styles.push("background-color: " + color);
resetStyles.push("background-color: unset");
} }
text += "%c" + part.text.replace(/%c/g, "%%c") + "%c";
text += "%c" + format.content.replace(/%c/g, "%%c") + "%c";
style_formats.push(styles.join(";"), resetStyles.join(";")); style_formats.push(styles.join(";"), resetStyles.join(";"));
} }
} }
@ -132,34 +190,26 @@ export class ConsoleAdapter implements Adapter {
} }
onMessage(message: Message) { onMessage(message: Message) {
let lines = message.text.formatted;
let prefix = "";
if (message.name) prefix = `[${message.name}]=>`;
if (browser) { if (browser) {
if (this.colors) { if (this.colors) {
let formats: string[] = []; const [text, formats] = this.format(message.text);
let text = lines
.map((line) => {
let [t, fmts] = this.formatLine(line);
formats.push(...fmts);
return prefix + t;
})
.join("\n");
// console.log(formats);
console.log(text, ...formats); console.log(text, ...formats);
} else { } else {
console.log(message.text.raw.join("\n")); console.log(Formatted.strip(message.text));
} }
} else { } else {
if (this.colors) { const text = this.colors
lines.forEach((line) => { ? this.format(message.text)[0]
let [text] = this.formatLine(line); : Formatted.strip(message.text);
console.log(prefix + text);
}); if (deno) {
//TODO: Deno specific thing
console.log(text);
} else if (typeof process !== "undefined") {
//NodeJS
process.stdout.write(text + "\n");
} else { } else {
message.text.raw.forEach((line) => console.log(prefix + line)); console.log(text);
} }
} }
} }

View File

@ -1,27 +1,21 @@
import { LoggingBase } from "./base.js"; import { Logging as LoggingClass } from "./base.js";
export { Logging } from "./base.js";
export { ConsoleAdapter } from "./consolewriter.js"; export { ConsoleAdapter } from "./consolewriter.js";
export { export { ILoggingOptions, INativeFunctions } from "./base.js";
LoggingBase,
LoggingBaseOptions,
removeColors,
withColor,
} from "./base.js";
export { export {
Adapter, Adapter,
LoggingTypes, LoggingTypes,
Message, Message,
FormatConfig, FormatConfig,
FormattedLine,
DefaultFormatConfig as DefaultColorMap, DefaultFormatConfig as DefaultColorMap,
FormattedText, IColors as Colors,
Colors,
Format, Format,
FormatTypes,
TerminalFormats, TerminalFormats,
Formatted,
IFormatted,
} from "./types.js"; } from "./types.js";
export { ObservableInterface } from "@hibas123/utils"; const Logging = new LoggingClass();
export let Logging: LoggingBase = undefined;
Logging = new LoggingBase();
export default Logging; export default Logging;

View File

@ -1,26 +1,19 @@
import { import Logging from "./index.js";
Logging,
LoggingBase, import { Logging as LoggingBase, LoggingTypes, Format } from "./index.js";
LoggingTypes,
Colors,
withColor,
} from "./index.js";
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.error("i", "am", "an", "error");
Logging.log( Logging.log(
"\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m TEST \x1b[31m\x1b[31m\x1b[31m" "\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m TEST \x1b[31m\x1b[31m\x1b[31m"
); );
Logging.log( Logging.log(Format.magenta("This text should be magenta!"), "This not!");
withColor(Colors.MAGENTA, "This text should be magenta!"), Logging.log(Format.magenta({ somekey: "Some value" }));
"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);
@ -29,10 +22,10 @@ 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({ name: "test2" });
cus2.log("Hello from custom Logger 2"); cus2.log("Hello from custom Logger 2");
let cus22 = new LoggingBase("test2"); let cus22 = new LoggingBase({ name: "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");
@ -53,7 +46,9 @@ Logging.warn("This should not be there 3");
Logging.warning("This should not be there 4"); 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.error("This should be there 2");
Logging.logLevel = LoggingTypes.Debug;
const c1 = Logging.getChild("child-level-1"); const c1 = Logging.getChild("child-level-1");
@ -63,25 +58,11 @@ const c2 = c1.getChild("child-level-2");
c2.log("Hello from Child 2"); c2.log("Hello from Child 2");
c2.addAdapter({
init: (obs) =>
obs.subscribe((msg) =>
console.log(
"Adapter adden on child level 2: ",
"---",
msg.name,
msg.text.raw
)
),
flush: () => undefined,
close: () => undefined,
});
c2.log("MSG from C2"); c2.log("MSG from C2");
c1.log("MSG from C1"); c1.log("MSG from C1");
Logging.log("MSG from root"); Logging.log("MSG from root");
const timer = Logging.time("timer1", "Test Timer"); const timer = Logging.time("timer1");
setTimeout(() => timer.end(), 1000); setTimeout(() => timer.end(), 1000);
const withoutFile = new LoggingBase({ const withoutFile = new LoggingBase({

View File

@ -1,9 +1,7 @@
import { ObservableInterface } from "@hibas123/utils";
export enum LoggingTypes { export enum LoggingTypes {
Debug, Debug,
Log, Log,
Warning, Warn,
Error, Error,
} }
@ -34,14 +32,7 @@ export const TerminalFormats = {
BgWhite: "\x1b[47m", BgWhite: "\x1b[47m",
}; };
export enum FormatTypes { export enum IColors {
COLOR,
BOLD,
UNDERSCORE,
BLINK,
}
export enum Colors {
NONE, NONE,
RED, RED,
GREEN, GREEN,
@ -52,56 +43,174 @@ export enum Colors {
WHITE, WHITE,
} }
export interface IFormatted<T = any> {
color?: IColors;
bgcolor?: IColors;
bold?: boolean;
italic?: boolean;
blink?: boolean;
underscore?: boolean;
content: T;
}
export class Formatted<T = any> implements IFormatted<T> {
static strip(formatted: IFormatted<string> | IFormatted<string>[]) {
if (Array.isArray(formatted)) {
return formatted.reduce((p, c) => p + c.content, "");
} else {
return formatted.content;
}
}
constructor(content?: T, apply?: Formatted) {
if (content instanceof Formatted) {
this.color = content.color;
this.bgcolor = content.bgcolor;
this.bold = content.bold;
this.italic = content.italic;
this.blink = content.blink;
this.underscore = content.underscore;
this.content = content.content;
} else {
this.content = content;
}
if (apply) {
this.color = apply.color;
this.bgcolor = apply.bgcolor;
this.bold = apply.bold;
this.italic = apply.italic;
this.blink = apply.blink;
this.underscore = apply.underscore;
}
}
color?: IColors;
bgcolor?: IColors;
bold?: boolean;
italic?: boolean;
blink?: boolean;
underscore?: boolean;
content: T;
_color(c: IColors) {
this.color = c;
return this;
}
_bgcolor(c: IColors) {
this.bgcolor = c;
return this;
}
_bold(active = true) {
this.bold = active;
return this;
}
_italic(active = true) {
this.italic = active;
return this;
}
_blink(active = true) {
this.blink = active;
return this;
}
}
const AddColorFormat = (color: IColors) => (content: any) => {
if (content instanceof Formatted) {
content.color = color;
return content;
} else {
const d = new Formatted(content);
d.color = color;
return d;
}
};
const AddBGColorFormat = (color: IColors) => (content: any) => {
if (content instanceof Formatted) {
content.bgcolor = color;
return content;
} else {
const d = new Formatted(content);
d.bgcolor = color;
return d;
}
};
export const Format = {
none: AddColorFormat(IColors.NONE),
red: AddColorFormat(IColors.RED),
green: AddColorFormat(IColors.GREEN),
yellow: AddColorFormat(IColors.YELLOW),
blue: AddColorFormat(IColors.BLUE),
magenta: AddColorFormat(IColors.MAGENTA),
cyan: AddColorFormat(IColors.CYAN),
white: AddColorFormat(IColors.WHITE),
bgnone: AddBGColorFormat(IColors.NONE),
bgred: AddBGColorFormat(IColors.RED),
bggreen: AddBGColorFormat(IColors.GREEN),
bgyellow: AddBGColorFormat(IColors.YELLOW),
bgblue: AddBGColorFormat(IColors.BLUE),
bgmagenta: AddBGColorFormat(IColors.MAGENTA),
bgcyan: AddBGColorFormat(IColors.CYAN),
bgwhite: AddBGColorFormat(IColors.WHITE),
bold: (content: any) => {
if (content instanceof Formatted) {
content.bold = true;
return content;
} else {
const d = new Formatted(content);
d.bold = true;
return d;
}
},
italic: (content: any) => {
if (content instanceof Formatted) {
content.italic = true;
return content;
} else {
const d = new Formatted(content);
d.italic = true;
return d;
}
},
};
export interface FormatConfig { export interface FormatConfig {
error: Format[]; error: Formatted;
warning: Format[]; warning: Formatted;
log: Format[]; log: Formatted;
debug: Format[]; debug: Formatted;
date: Format[]; date: Formatted;
file: Format[]; file: Formatted;
names: Formatted;
names_delimiter: Formatted;
} }
function colorFormat(color: Colors) {
return {
type: FormatTypes.COLOR,
color,
};
}
const boldFormat = {
type: FormatTypes.BOLD,
};
export class DefaultFormatConfig implements FormatConfig { export class DefaultFormatConfig implements FormatConfig {
error = [colorFormat(Colors.RED), boldFormat]; error = new Formatted()._color(IColors.RED)._bold();
warning = [colorFormat(Colors.YELLOW), boldFormat]; warning = new Formatted()._color(IColors.YELLOW)._bold();
log = [colorFormat(Colors.NONE), boldFormat]; log = new Formatted()._color(IColors.NONE)._bold();
debug = [colorFormat(Colors.CYAN), boldFormat]; debug = new Formatted()._color(IColors.CYAN)._bold();
date = [colorFormat(Colors.NONE)]; date = new Formatted()._color(IColors.NONE);
file = [colorFormat(Colors.NONE)]; file = new Formatted()._color(IColors.NONE);
names = new Formatted()._bold()._color(IColors.GREEN);
names_delimiter = new Formatted(" -> ")._bold();
} }
export interface Format {
type: FormatTypes;
color?: Colors;
}
export interface FormattedText {
text: string;
formats: Format[];
}
export type FormattedLine = FormattedText[];
export interface Message { export interface Message {
type: LoggingTypes; type: LoggingTypes;
name?: string; names?: string[];
text: { text: IFormatted<string>[];
raw: string[];
formatted: FormattedLine[];
};
date: Date; date: Date;
file: string; file: string;
} }
@ -111,11 +220,13 @@ export interface Adapter {
* This function initialises the Adapter. It might be called multiple times, when added to multiple instances * This function initialises the Adapter. It might be called multiple times, when added to multiple instances
* @param observable An observable to subscribe to messages * @param observable An observable to subscribe to messages
*/ */
init(observable: ObservableInterface<Message>): void | Promise<void>; init(): void | Promise<void>;
flush(sync: true): void; flush(sync: true): void;
flush(sync: false): void | Promise<void>; flush(sync: false): void | Promise<void>;
onMessage(message: Message): void;
/** /**
* When a close function is available, it will be called when no logging instance references it anymore. * When a close function is available, it will be called when no logging instance references it anymore.
* *