Compare commits

...

28 Commits

Author SHA1 Message Date
3de5f368ef Add typescript declarations 2023-11-22 22:27:54 +01:00
e0b51625d8 Make it ESM only 2023-11-22 22:18:40 +01:00
153aca0ccb Add missing await to addAdapter init 2021-05-19 11:41:14 +02:00
7ca0c4fd72 Some performance optimizations 2021-05-18 14:10:59 +02:00
8e183ac1a5 Add adapter based control of log level 2021-05-18 09:10:06 +02:00
feed4626e6 Fix bugf 2021-05-09 22:33:47 +02:00
9d4e521619 Fix error with non existing close on adapter 2021-05-08 22:53:12 +02:00
7d75f65dd3 Bind functions of Logging to this 2021-05-08 22:28:06 +02:00
eeed068ddd Add exports 2021-05-08 22:24:00 +02:00
6daf815ea8 Change name of class 2021-05-08 22:18:44 +02:00
357b98c69a Changing name again 2021-05-08 22:15:56 +02:00
96d7808f35 V3 2021-05-08 21:47:00 +02:00
176d37249d Making filename configurable after creation 2020-09-30 17:28:32 +02:00
bcff79fc90 Making filename output optional 2020-09-30 16:46:16 +02:00
b92caf6468 Add ESModule support 2020-08-26 11:55:57 +02:00
f34800d725 Use private fields 2020-05-18 17:06:18 +02:00
1fd8da459b timeEnd returns measured time in ms 2020-05-05 18:33:33 +02:00
4420fb13ea Fix bug with undefined file causing logging to throw an exception 2020-04-21 01:03:54 +02:00
511bdf127f Make consolewriter colors default to true 2020-04-20 17:15:23 +02:00
7ae1c3e16e Remove bad console.log 2020-04-15 19:59:24 +02:00
ca896c1c34 Switch file resolving 2020-04-15 19:51:15 +02:00
30f5e241ae Improve get child behavior 2020-04-11 17:39:20 +02:00
a1cd860688 Changing naming scheme of getChild 2020-04-11 16:48:27 +02:00
f51f4e2aad Make getStack to clean up event when error occurs 2020-04-11 15:05:17 +02:00
fa7a168f17 Fixing bug with time using wrong logging instance 2020-04-09 18:39:43 +02:00
2eb9356a9d Add get child 2020-04-09 18:33:47 +02:00
9182efe7e7 Fixing some problems with the adapter API 2020-04-09 18:00:59 +02:00
94d9731cdd Running everything through prettier 2020-04-09 17:50:30 +02:00
15 changed files with 6589 additions and 1654 deletions

2
.gitignore vendored
View File

@ -2,4 +2,4 @@ node_modules/
logs/ logs/
yarn.lock yarn.lock
out/ out/
.history/ esm/

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/out/test.js",
"preLaunchTask": "tsc: build - tsconfig.json",
"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.
@ -31,57 +31,66 @@ 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: "custom", // default undefined name |
console: false // default true {
}); name: "custom", // default undefined
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.
``` javascript ```javascript
const Demo = new LoggingBase("Demo"); const Demo = new LoggingBase("Demo");
Demo.addAdapter(new DemoAdapter({ color: "rainbow" })); Demo.addAdapter(new DemoAdapter({ color: "rainbow" }));
``` ```
The adapters need to provide a very simple Interface: 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>;
flush(sync: true): void; onMessage(message: Message): void;
flush(sync: false): void | Promise<void>;
flush(sync: true): void;
flush(sync: false): void | Promise<void>;
} }
interface Message { interface Message {
type: LoggingTypes; type: LoggingTypes;
name?:string; names?: string[];
text: { text: IFormatted;
raw: string[], date: Date;
formatted: string[] file: string;
}; }
date: Date;
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

54
benchmark.js Normal file
View File

@ -0,0 +1,54 @@
const { LoggingBase } = require("./out/index.js");
let results = {};
function benchmark(name, count, runner) {
console.profile(name);
const start = process.hrtime.bigint()
runner(count);
const diffNS = process.hrtime.bigint() - start;
const diffMS = Number(diffNS / 1000n / 1000n);
console.profileEnd(name)
results[name]= {
count,
time: diffMS,
timePerI: (diffMS / count).toFixed(4)
}
}
benchmark("simple", 10000000, (cnt) => {
const l = new LoggingBase({
console: false
});
for (let i = 0; i < cnt; i++) {
l.log("simple log")
}
})
benchmark("complex", 1000000, (cnt) => {
const l = new LoggingBase({
console: false
});
for (let i = 0; i < cnt; i++) {
l.log("complex log", {
a: 1,
b: {
c:"test"
}
})
}
})
benchmark("very long", 10000000, (cnt) => {
const l = new LoggingBase({
console: false
});
const longText = "complex log".repeat(100);
for (let i = 0; i < cnt; i++) {
l.log(longText)
}
})
console.table(results)

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.1.2",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"hooks": {
"prepublish": "deno_build.js"
},
"root": "esm",
"files": [
"esm/**/*.ts",
"esm/**/*.js",
"esm/README.md"
]
}

5925
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,18 @@
{ {
"name": "@hibas123/logging", "name": "@hibas123/logging",
"version": "2.2.2", "version": "4.0.1",
"description": "", "description": "",
"main": "out/index.js", "type": "module",
"types": "out/index.d.ts", "main": "esm/index.js",
"types": "esm/index.d.ts",
"module": "esm/index.js",
"scripts": { "scripts": {
"prepublish": "tsc", "prepublishOnly": "npm run build",
"build": "tsc", "build": "tsc",
"watch-ts": "tsc --watch", "test": "tsc && node esm/test.js",
"watch-js": "nodemon out/test.js", "postpublish": "denreg publish",
"watch": "concurrently npm:watch-*", "bench": "tsc && node benchmark.js",
"test": "node out/test.js" "prof": "tsc && ndb --prof benchmark.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -20,16 +22,15 @@
"license": "MIT", "license": "MIT",
"files": [ "files": [
"src/", "src/",
"out/", "esm/",
"tsconfig.json", "tsconfig.json",
"readme.md" "readme.md"
], ],
"devDependencies": { "devDependencies": {
"concurrently": "^5.1.0", "concurrently": "^6.0.2",
"nodemon": "^2.0.2", "ndb": "^1.1.5",
"typescript": "^3.8.3" "nodemon": "^2.0.7",
}, "ts-node": "^9.1.1",
"dependencies": { "typescript": "^4.2.4"
"@hibas123/utils": "^2.2.3"
} }
} }

View File

@ -1,37 +1,19 @@
import { Observable } from "@hibas123/utils"; import { ConsoleAdapter } from "./consolewriter.js";
import { ConsoleAdapter } from "./consolewriter"; import inspect from "./inspect.js";
import inspect from "./inspect";
import { import {
Adapter, Adapter,
LoggingTypes,
Message,
FormatConfig,
DefaultFormatConfig, DefaultFormatConfig,
Format, Format,
FormatTypes, FormatConfig,
Colors, Formatted,
FormattedText, ILoggingInterface,
FormattedLine, LoggingTypes,
} from "./types"; Message,
import Logging from "."; } from "./types.js";
const browser = typeof window !== "undefined"; const browser = typeof window !== "undefined";
export function removeColors(text: string) { export interface ILoggingOptions {
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;
}
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
*/ */
@ -40,174 +22,110 @@ export interface LoggingBaseOptions {
* Prints output to console * Prints output to console
*/ */
console: boolean; console: boolean;
/**
* Enables printing of calling file
*/
resolve_filename: boolean;
} }
export class LoggingBase { export interface INativeFunctions {
private _formatMap: FormatConfig = new DefaultFormatConfig(); startTimer(): any;
endTimer(start: any): number;
}
public set formatMap(value: FormatConfig) { export const DefaultNativeFunctions = {
this._formatMap = value; startTimer: () => {
}
private adapter = new Set<Adapter>();
private adapter_init: Promise<void>[] = [];
private timerMap = new Map<string, { name: string; start: any }>();
private messageObservable = new Observable<Message>();
protected _name: string;
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) {
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,
...opt,
};
if (config.name) this._name = config.name;
for (let key in this) {
if (typeof this[key] === "function")
this[key] = (<any>this[key]).bind(this);
}
if (config.console) {
this.addAdapter(new 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);
}
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<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(() => {});
}
}
public close() {
this.adapter.forEach((adapter) =>
adapter.close ? adapter.close() : undefined
);
}
public waitForSetup() {
return Promise.all(this.adapter_init);
}
debug(...message: any[]) {
if (this._logLevel <= LoggingTypes.Debug)
this.message(LoggingTypes.Debug, message);
}
log(...message: any[]) {
if (this._logLevel <= LoggingTypes.Log)
this.message(LoggingTypes.Log, message);
}
warning(...message: any[]) {
if (this._logLevel <= LoggingTypes.Warning)
this.message(LoggingTypes.Warning, message);
}
warn(...message: any[]) {
if (this._logLevel <= LoggingTypes.Warning)
this.message(LoggingTypes.Warning, 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)
);
}
}
errorMessage(...message: any[]) {
if (this._logLevel <= LoggingTypes.Error)
this.message(LoggingTypes.Error, message);
}
protected getCurrentTime(): any {
if (browser && window.performance && window.performance.now) { if (browser && window.performance && window.performance.now) {
return window.performance.now(); return window.performance.now();
} else { } else {
return Date.now(); return Date.now();
} }
} },
endTimer: (start: any) => {
/**
* The time difference in milliseconds (fractions allowed!)
* @param start Start time from getCurrentTime
*/
protected getTimeDiff(start: any) {
if (browser && window.performance && window.performance.now) { if (browser && window.performance && window.performance.now) {
return window.performance.now() - start; return window.performance.now() - start;
} else { } else {
return Date.now() - start; return Date.now() - start;
} }
},
} as INativeFunctions;
const consoleAdapter = new ConsoleAdapter();
declare var process: { cwd: () => string };
const PROJECT_ROOT = typeof process !== "undefined" ? process.cwd() : undefined;
const InitialisedAdapters = Symbol("@hibas123/logging:initialisedAdapters");
export abstract class LoggingInterface implements ILoggingInterface {
#names: string[];
#timerMap = new Map<string, { name: string; start: any }>();
get names() {
return [...this.#names];
} }
time(id?: string, name = id) { protected abstract message(
if (!id) { type: LoggingTypes,
id = Math.floor(Math.random() * 899999 + 100000).toString(); names: string[],
} message: any[],
caller?: { file: string; line: number; column?: number }
): void;
this.timerMap.set(id, { constructor(names: string[]) {
name, this.#names = names;
start: this.getCurrentTime(),
for (const key in this) {
if (typeof this[key] === "function") {
this[key] = (this[key] as never as Function).bind(this);
}
}
}
debug(...message: any[]) {
this.message(LoggingTypes.Debug, this.#names, message);
}
log(...message: any[]) {
this.message(LoggingTypes.Log, this.#names, message);
}
warning(...message: any[]) {
this.message(LoggingTypes.Warn, this.#names, message);
}
warn(...message: any[]) {
this.message(LoggingTypes.Warn, this.#names, message);
}
error(error: Error | string, ...message: any[]) {
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, this.#names, [
error,
...message,
"\n",
e.stack,
"\n",
]);
} else {
this.message(
LoggingTypes.Error,
this.#names,
[error.message, "\n", error.stack, "\n", ...message],
getCallerFromExisting(error)
);
}
}
time(id?: string) {
if (!id) id = Math.floor(Math.random() * 899999 + 100000).toString();
this.#timerMap.set(id, {
name: id,
start: LoggingBase.nativeFunctions.startTimer(),
}); });
return { return {
@ -217,167 +135,278 @@ 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 = LoggingBase.nativeFunctions.endTimer(timer.start);
Logging.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 -1;
}
abstract getChild(name: string): ILoggingInterface;
}
export class LoggingBase extends LoggingInterface {
private static [InitialisedAdapters] = new Map<Adapter, number>();
public static nativeFunctions: INativeFunctions = DefaultNativeFunctions;
static DecoupledLogging = class extends LoggingInterface {
#lg: LoggingBase;
constructor(names: string[], lg: LoggingBase) {
super(names);
this.#lg = lg;
}
message(...params: [any, any, any, any]) {
this.#lg.message(...params);
}
getChild(name: string) {
return new LoggingBase.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);
} }
} }
private message( async addAdapter(adapter: Adapter) {
type: LoggingTypes, const init = adapter.init();
message: any[],
caller?: { file: string; line: number }
) {
let date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "");
let file_raw = caller || getCallerFile(); const add = () => {
let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`; this.#adapters.add(adapter);
if (LoggingBase[InitialisedAdapters].has(adapter)) {
LoggingBase[InitialisedAdapters].set(
adapter,
LoggingBase[InitialisedAdapters].get(adapter) + 1
);
} else {
LoggingBase[InitialisedAdapters].set(adapter, 1);
}
};
if (!init) {
add();
} else {
await Promise.resolve(init).then(add);
}
}
getChild(name: string): ILoggingInterface {
return new LoggingBase.DecoupledLogging([...this.names, name], this);
}
protected message(
type: LoggingTypes,
names: string[],
message: any[],
caller?: { file: string; line: number; column?: number }
) {
if (this.#logLevel > type) return;
if (this.#closed) {
//TODO: Maybe error?
return;
}
const date = new Date();
const isoStr = date.toISOString();
const date_str = isoStr.substring(0, 10) + " " + isoStr.substring(11, 19);
let file: string | undefined = undefined;
if (this.#resolve_filename) {
let file_raw = caller;
if (!file_raw) {
try {
file_raw = getCallerFile();
} catch (err) {
file_raw = {
file: "<unknown>",
line: 0,
column: 0,
};
}
}
if (
PROJECT_ROOT &&
file_raw.file &&
file_raw.file.startsWith(PROJECT_ROOT)
) {
let newF = file_raw.file.substring(PROJECT_ROOT.length);
if (newF.startsWith("/") || newF.startsWith("\\"))
newF = newF.substring(1);
file_raw.file = newF;
}
file = `${file_raw.file || "<unknown>"}:${file_raw.line}:${
file_raw.column || 0
}`;
}
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_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;
}
} }
if (typeof e !== "string") }
e = inspect(e, { nameFormatted.push(new Formatted("]"));
colors: true, }
showHidden: true,
depth: 3, let linePrefix = [
}) as string; 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 formattedMessage: Formatted<string>[] = [...linePrefix];
message.forEach((msg, idx) => {
let format: Formatted;
if (msg instanceof Formatted) {
format = msg;
msg = msg.content;
} else {
format = new Formatted();
} }
removeColors(e) if (typeof msg !== "string") {
.split("\n") msg = inspect(msg, {
.map((text, index, { length }) => { colors: false, //TODO: Maybe change when changing the removeColors to return formatted text?
line.push({ text, formats }); showHidden: true,
if (index < length - 1) { depth: 3,
newLine(); }) as string;
}
});
if (!e.endsWith("\n") && i < message.length - 1) {
line.push({ text: " ", formats: [] });
} }
// removeColors(msg) // Remove colors is uncommented for now, since there are no real benefits of having it and it reduces performance
msg.split("\n").forEach((text, index, { length }) => {
if (index != length - 1) {
formattedMessage.push(
new Formatted(text + "\n", format),
...linePrefix
);
} else {
formattedMessage.push(new Formatted(text, format));
}
});
formattedMessage.push(new Formatted(" "));
}); });
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) => {
if (adapter.level <= type) {
adapter.onMessage(msg);
}
});
} }
}
const colorSymbol = Symbol("color"); async close() {
if (this.#closed) return;
this.#closed = true;
export interface ColorFormat { for (const adapter of this.#adapters) {
[colorSymbol]: Colors; const cnt = LoggingBase[InitialisedAdapters].get(adapter);
value: any; if (!cnt) {
} //TODO: should not happen!
} else {
export function withColor(color: Colors, value: any): ColorFormat { if (cnt <= 1) {
return { if (adapter.close) await adapter.close();
[colorSymbol]: color, LoggingBase[InitialisedAdapters].delete(adapter);
value, } else {
}; LoggingBase[InitialisedAdapters].set(adapter, cnt - 1);
}
}
}
}
} }
function getStack() { function getStack() {
// Save original Error.prepareStackTrace // Save original Error.prepareStackTrace
let origPrepareStackTrace = (<any>Error).prepareStackTrace; let origPrepareStackTrace = (<any>Error).prepareStackTrace;
try {
// Override with function that just returns `stack`
(<any>Error).prepareStackTrace = function (_, stack) {
return stack;
};
// Create a new `Error`, which automatically gets `stack`
let err = new Error();
// Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
let stack: any[] = <any>err.stack;
// Override with function that just returns `stack`
(<any>Error).prepareStackTrace = function (_, stack) {
return stack; return stack;
}; } finally {
// Restore original `Error.prepareStackTrace`
// Create a new `Error`, which automatically gets `stack` (<any>Error).prepareStackTrace = origPrepareStackTrace;
let err = new Error(); }
// Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
let stack: any[] = <any>err.stack;
// Restore original `Error.prepareStackTrace`
(<any>Error).prepareStackTrace = origPrepareStackTrace;
// Remove superfluous function call on stack
stack.shift(); // getStack --> Error
return stack;
}
function baseName(path) {
return path.split(/[\\/]/).pop();
} }
function getCallerFile() { function getCallerFile() {
@ -388,29 +417,57 @@ function getCallerFile() {
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 { return {
file: baseName(caller_file.getFileName()), file: caller_file.getFileName(),
line: caller_file.getLineNumber(), line: caller_file.getLineNumber(),
column: caller_file.getColumnNumber(),
}; };
}
} }
} catch (err) {} } catch (err) {}
return { file: undefined, line: 0 }; return { file: undefined, line: 0 };
} }
function getCallerFromExisting(err: Error): { file: string; line: number } { function getCallerFromExisting(err: Error): {
file: string;
line: number;
column?: number;
} {
if (!err || !err.stack) return { file: "NOFILE", line: 0 }; if (!err || !err.stack) return { file: "NOFILE", line: 0 };
let lines = err.stack.split("\n"); let lines = err.stack.split("\n");
lines.shift(); // removing first line lines.shift(); // removing first line
while (lines.length > 0) { while (lines.length > 0) {
let line = lines.shift(); let line = lines.shift();
let matches = line.match(/[a-zA-Z_-]+[.][a-zA-Z_-]+[:][0-9]+/g); let matches = line.match(
/[<]?([a-zA-Z]:)?([\/\\]?[a-zA-Z_-])+[.>][a-zA-Z_-]*([:][0-9]+)+/g
);
if (matches && matches.length > 0) { if (matches && matches.length > 0) {
let [f, line] = matches[0].split(":"); let match = matches[0].trim();
let locationString = match.match(/([:][0-9]+)+$/gm)[0];
let line: number;
let column: number;
if (locationString) {
match = match.slice(0, match.length - locationString.length);
locationString = locationString.substring(1);
[line, column] = locationString.split(":").map(Number);
}
let file = match;
return { return {
file: f, file,
line: Number(line), line,
column,
}; };
} }
} }
} }
export function removeColors(text: string) {
text = text.replace(
// Putting regex here directly instead of externally actually improves performance. The cause of that is not clear for now.
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
""
);
return text;
}

View File

@ -1,166 +1,221 @@
import { ObservableInterface } from "@hibas123/utils"; import { Colors } from "./index.js";
import { Colors } from "./index";
import { import {
<<<<<<< HEAD
Adapter,
Message,
FormattedLine,
TerminalFormats,
FormatTypes
=======
Adapter, Adapter,
Message, Message,
FormattedLine,
TerminalFormats, TerminalFormats,
FormatTypes, Formatted,
>>>>>>> facb7e7b403e1dfaa8790426259e6effb0618efb IFormatted,
} from "./types"; LoggingTypes,
} from "./types.js";
const browser = typeof window !== "undefined"; declare const Deno: any;
declare const process: any;
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 {
init(observable: ObservableInterface<Message>) { level: LoggingTypes = LoggingTypes.Debug;
observable.subscribe(this.onMessage.bind(this)); constructor(private colors: boolean = true) {}
}
<<<<<<< HEAD init() {}
flush() {}
=======
flush() {} flush() {}
>>>>>>> facb7e7b403e1dfaa8790426259e6effb0618efb
// TODO: Check if required! setLevel(level: LoggingTypes) {
// private escape(text: string): string { this.level = level;
// return text }
// .replace(/%s/g, "%%s") // TODO: Check if required!
// .replace(/%c/g, "%%c") // private escape(text: string): string {
// } // return text
// .replace(/%s/g, "%%s")
// .replace(/%c/g, "%%c")
// }
private formatLine(line: FormattedLine): [string, string[] | undefined] { private format(
let text = ""; formatted: IFormatted<string>[]
let style_formats: string[] = []; ): [string, string[] | undefined] {
let text = "";
let style_formats: string[] = [];
if (!browser) { if (!browser) {
for (let part of line) { // NodeJS or Deno
let formats = ""; for (const format of formatted) {
for (let format of part.formats) { let formats = "";
switch (format.type) { if (format.bold) {
case FormatTypes.BOLD: formats += TerminalFormats.Bold;
formats += TerminalFormats.Bold; }
break;
case FormatTypes.UNDERSCORE: if (format.blink) {
formats += TerminalFormats.Underscore; formats += TerminalFormats.Blink;
break; }
case FormatTypes.BLINK:
formats += TerminalFormats.Blink; if (format.underscore) {
break; formats += TerminalFormats.Underscore;
case FormatTypes.COLOR: }
switch (format.color) {
case Colors.RED: if (format.color) {
formats += TerminalFormats.FgRed; switch (format.color) {
break; case Colors.RED:
case Colors.GREEN: formats += TerminalFormats.FgRed;
formats += TerminalFormats.FgGreen; break;
break; case Colors.GREEN:
case Colors.YELLOW: formats += TerminalFormats.FgGreen;
formats += TerminalFormats.FgYellow; break;
break; case Colors.YELLOW:
case Colors.BLUE: formats += TerminalFormats.FgYellow;
formats += TerminalFormats.FgBlue; break;
break; case Colors.BLUE:
case Colors.MAGENTA: formats += TerminalFormats.FgBlue;
formats += TerminalFormats.FgMagenta; break;
break; case Colors.MAGENTA:
case Colors.CYAN: formats += TerminalFormats.FgMagenta;
formats += TerminalFormats.FgCyan; break;
break; case Colors.CYAN:
case Colors.WHITE: formats += TerminalFormats.FgCyan;
formats += TerminalFormats.FgWhite; break;
break; case Colors.WHITE:
} formats += TerminalFormats.FgWhite;
break; break;
} }
} }
text += formats + part.text + TerminalFormats.Reset;
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;
}
}
text += formats + format.content + TerminalFormats.Reset;
// (formats.length > 0 ? TerminalFormats.Reset : ""); //TODO: Benchmark if this is better
}
} else {
for (const format of formatted) {
let styles: string[] = [];
let resetStyles: string[] = [];
if (format.bold) {
styles.push("font-weight: bold;");
resetStyles.push("font-weight: unset");
}
if (format.underscore) {
styles.push("text-decoration: underline");
resetStyles.push("text-decoration: unset");
}
if (format.blink) {
styles.push("text-decoration: blink");
resetStyles.push("text-decoration: unset");
}
if (format.color) {
let color = "";
switch (format.color) {
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;
}
styles.push("color: " + color);
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;
}
styles.push("background-color: " + color);
resetStyles.push("background-color: unset");
}
text += "%c" + format.content.replace(/%c/g, "%%c") + "%c";
style_formats.push(styles.join(";"), resetStyles.join(";"));
}
} }
} else {
for (let part of line) { return [text, style_formats];
let styles: string[] = []; }
let resetStyles: string[] = [];
for (let format of part.formats) { onMessage(message: Message) {
switch (format.type) { if (browser) {
case FormatTypes.BOLD: if (this.colors) {
styles.push("font-weight: bold;"); const [text, formats] = this.format(message.text);
resetStyles.push("font-weight: unset"); console.log(text, ...formats);
break; } else {
case FormatTypes.UNDERSCORE: console.log(Formatted.strip(message.text));
styles.push("text-decoration: underline"); }
resetStyles.push("text-decoration: unset"); } else {
break; const text = this.colors
case FormatTypes.BLINK: ? this.format(message.text)[0]
styles.push("text-decoration: blink"); : Formatted.strip(message.text);
resetStyles.push("text-decoration: unset");
break; if (deno) {
case FormatTypes.COLOR: //TODO: Deno specific thing
let color = ""; console.log(text);
switch (format.color) { } else if (typeof process !== "undefined") {
case Colors.RED: //NodeJS
color = "red"; process.stdout.write(text + "\n");
break; } else {
case Colors.GREEN: console.log(text);
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;
}
styles.push("color: " + color);
resetStyles.push("color: unset");
break;
}
}
text += "%c" + part.text.replace(/%c/g, "%%c") + "%c";
style_formats.push(styles.join(";"), resetStyles.join(";"));
} }
} }
return [text, style_formats];
}
onMessage(message: Message) {
let lines = message.text.formatted;
let prefix = "";
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 prefix + t;
})
.join("\n");
// console.log(formats);
console.log(text, ...formats);
} else {
lines.forEach(line => {
let [text] = this.formatLine(line);
console.log(prefix + text);
});
}
}
} }

View File

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

View File

@ -1,12 +1,11 @@
/** /**
* Module exports. * Module exports.
*/ */
interface InspectOptions { interface InspectOptions {
depth: number; depth: number;
colors: boolean; colors: boolean;
showHidden: boolean; showHidden: boolean;
} }
/** /**
@ -19,397 +18,440 @@ interface InspectOptions {
*/ */
/* legacy: obj, showHidden, depth, colors*/ /* legacy: obj, showHidden, depth, colors*/
export default function inspect(obj: any, opts: Partial<InspectOptions>) { export default function inspect(obj: any, opts: Partial<InspectOptions>) {
// default options // default options
let ctx = { let ctx = {
seen: [], seen: [],
stylize: stylizeNoColor, stylize: stylizeNoColor,
depth: undefined, depth: undefined,
colors: undefined, colors: undefined,
showHidden: undefined, showHidden: undefined,
customInspect: undefined customInspect: undefined,
}; };
// legacy... // legacy...
if (arguments.length >= 3) ctx.depth = arguments[2]; if (arguments.length >= 3) ctx.depth = arguments[2];
if (arguments.length >= 4) ctx.colors = arguments[3]; if (arguments.length >= 4) ctx.colors = arguments[3];
if (isBoolean(opts)) { if (isBoolean(opts)) {
// legacy... // legacy...
ctx.showHidden = opts; ctx.showHidden = opts;
} else if (opts) { } else if (opts) {
// got an "options" object // got an "options" object
_extend(ctx, opts); _extend(ctx, opts);
} }
// set default options // set default options
if (isUndefined(ctx.showHidden)) ctx.showHidden = false; if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
if (isUndefined(ctx.depth)) ctx.depth = 2; if (isUndefined(ctx.depth)) ctx.depth = 2;
if (isUndefined(ctx.colors)) ctx.colors = false; if (isUndefined(ctx.colors)) ctx.colors = false;
if (isUndefined(ctx.customInspect)) ctx.customInspect = true; if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
if (ctx.colors) ctx.stylize = stylizeWithColor; if (ctx.colors) ctx.stylize = stylizeWithColor;
return formatValue(ctx, obj, ctx.depth); return formatValue(ctx, obj, ctx.depth);
} }
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = { inspect.colors = {
'bold': [1, 22], bold: [1, 22],
'italic': [3, 23], italic: [3, 23],
'underline': [4, 24], underline: [4, 24],
'inverse': [7, 27], inverse: [7, 27],
'white': [37, 39], white: [37, 39],
'grey': [90, 39], grey: [90, 39],
'black': [30, 39], black: [30, 39],
'blue': [34, 39], blue: [34, 39],
'cyan': [36, 39], cyan: [36, 39],
'green': [32, 39], green: [32, 39],
'magenta': [35, 39], magenta: [35, 39],
'red': [31, 39], red: [31, 39],
'yellow': [33, 39] yellow: [33, 39],
}; };
// Don't use 'blue' not visible on cmd.exe // Don't use 'blue' not visible on cmd.exe
inspect.styles = { inspect.styles = {
'special': 'cyan', special: "cyan",
'number': 'yellow', number: "yellow",
'boolean': 'yellow', boolean: "yellow",
'undefined': 'grey', undefined: "grey",
'null': 'bold', null: "bold",
'string': 'green', string: "green",
'date': 'magenta', date: "magenta",
// "name": intentionally not styling // "name": intentionally not styling
'regexp': 'red' regexp: "red",
}; };
function stylizeNoColor(str, styleType) { function stylizeNoColor(str, styleType) {
return str; return str;
} }
function isBoolean(arg) { function isBoolean(arg) {
return typeof arg === 'boolean'; return typeof arg === "boolean";
} }
function isUndefined(arg) { function isUndefined(arg) {
return arg === void 0; return arg === void 0;
} }
function stylizeWithColor(str, styleType) { function stylizeWithColor(str, styleType) {
var style = inspect.styles[styleType]; var style = inspect.styles[styleType];
if (style) { if (style) {
return '\u001b[' + inspect.colors[style][0] + 'm' + str + return (
'\u001b[' + inspect.colors[style][1] + 'm'; "\u001b[" +
} else { inspect.colors[style][0] +
return str; "m" +
} str +
"\u001b[" +
inspect.colors[style][1] +
"m"
);
} else {
return str;
}
} }
function isFunction(arg) { function isFunction(arg) {
return typeof arg === 'function'; return typeof arg === "function";
} }
function isString(arg) { function isString(arg) {
return typeof arg === 'string'; return typeof arg === "string";
} }
function isNumber(arg) { function isNumber(arg) {
return typeof arg === 'number'; return typeof arg === "number";
} }
function isNull(arg) { function isNull(arg) {
return arg === null; return arg === null;
} }
function hasOwn(obj, prop) { function hasOwn(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop); return Object.prototype.hasOwnProperty.call(obj, prop);
} }
function isRegExp(re) { function isRegExp(re) {
return isObject(re) && objectToString(re) === '[object RegExp]'; return isObject(re) && objectToString(re) === "[object RegExp]";
} }
function isObject(arg) { function isObject(arg) {
return typeof arg === 'object' && arg !== null; return typeof arg === "object" && arg !== null;
} }
function isError(e) { function isError(e) {
return isObject(e) && return (
(objectToString(e) === '[object Error]' || e instanceof Error); isObject(e) &&
(objectToString(e) === "[object Error]" || e instanceof Error)
);
} }
function isDate(d) { function isDate(d) {
return isObject(d) && objectToString(d) === '[object Date]'; return isObject(d) && objectToString(d) === "[object Date]";
} }
function objectToString(o) { function objectToString(o) {
return Object.prototype.toString.call(o); return Object.prototype.toString.call(o);
} }
function arrayToHash(array) { function arrayToHash(array) {
var hash = {}; var hash = {};
array.forEach(function (val, idx) { array.forEach(function (val, idx) {
hash[val] = true; hash[val] = true;
}); });
return hash; return hash;
} }
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = []; var output = [];
for (var i = 0, l = value.length; i < l; ++i) { for (var i = 0, l = value.length; i < l; ++i) {
if (hasOwn(value, String(i))) { if (hasOwn(value, String(i))) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, output.push(
String(i), true)); formatProperty(
} else { ctx,
output.push(''); value,
} recurseTimes,
} visibleKeys,
keys.forEach(function (key) { String(i),
if (!key.match(/^\d+$/)) { true
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, )
key, true)); );
} } else {
}); output.push("");
return output; }
}
keys.forEach(function (key) {
if (!key.match(/^\d+$/)) {
output.push(
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)
);
}
});
return output;
} }
function formatError(value) { function formatError(value) {
return '[' + Error.prototype.toString.call(value) + ']'; return "[" + Error.prototype.toString.call(value) + "]";
} }
function formatValue(ctx, value, recurseTimes) { function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions. // Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it // Check that value is an object with an inspect function on it
if (ctx.customInspect && if (
value && ctx.customInspect &&
isFunction(value.inspect) && value &&
// Filter out the util module, it's inspect function is special isFunction(value.inspect) &&
value.inspect !== inspect && // Filter out the util module, it's inspect function is special
// Also filter out any prototype objects using the circular check. value.inspect !== inspect &&
!(value.constructor && value.constructor.prototype === value)) { // Also filter out any prototype objects using the circular check.
var ret = value.inspect(recurseTimes, ctx); !(value.constructor && value.constructor.prototype === value)
if (!isString(ret)) { ) {
ret = formatValue(ctx, ret, recurseTimes); var ret = value.inspect(recurseTimes, ctx);
} if (!isString(ret)) {
return ret; ret = formatValue(ctx, ret, recurseTimes);
} }
return ret;
}
// Primitive types cannot have properties // Primitive types cannot have properties
var primitive = formatPrimitive(ctx, value); var primitive = formatPrimitive(ctx, value);
if (primitive) { if (primitive) {
return primitive; return primitive;
} }
// Look up the keys of the object. // Look up the keys of the object.
var keys = Object.keys(value); var keys = Object.keys(value);
var visibleKeys = arrayToHash(keys); var visibleKeys = arrayToHash(keys);
try { try {
if (ctx.showHidden && Object.getOwnPropertyNames) { if (ctx.showHidden && Object.getOwnPropertyNames) {
keys = Object.getOwnPropertyNames(value); keys = Object.getOwnPropertyNames(value);
} }
} catch (e) { } catch (e) {
// ignore // ignore
} }
// IE doesn't make error fields non-enumerable // IE doesn't make error fields non-enumerable
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
if (isError(value) if (
&& (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { isError(value) &&
return formatError(value); (keys.indexOf("message") >= 0 || keys.indexOf("description") >= 0)
} ) {
return formatError(value);
}
// Some type of object without properties can be shortcutted. // Some type of object without properties can be shortcutted.
if (keys.length === 0) { if (keys.length === 0) {
if (isFunction(value)) { if (isFunction(value)) {
var name = value.name ? ': ' + value.name : ''; var name = value.name ? ": " + value.name : "";
return ctx.stylize('[Function' + name + ']', 'special'); return ctx.stylize("[Function" + name + "]", "special");
} }
if (isRegExp(value)) { if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); return ctx.stylize(RegExp.prototype.toString.call(value), "regexp");
} }
if (isDate(value)) { if (isDate(value)) {
return ctx.stylize(Date.prototype.toString.call(value), 'date'); return ctx.stylize(Date.prototype.toString.call(value), "date");
} }
if (isError(value)) { if (isError(value)) {
return formatError(value); return formatError(value);
} }
} }
var base = '', array = false, braces = ['{', '}']; var base = "",
array = false,
braces = ["{", "}"];
// Make Array say that they are Array // Make Array say that they are Array
if (Array.isArray(value)) { if (Array.isArray(value)) {
array = true; array = true;
braces = ['[', ']']; braces = ["[", "]"];
} }
// Make functions say that they are functions // Make functions say that they are functions
if (isFunction(value)) { if (isFunction(value)) {
var n = value.name ? ': ' + value.name : ''; var n = value.name ? ": " + value.name : "";
base = ' [Function' + n + ']'; base = " [Function" + n + "]";
} }
// Make RegExps say that they are RegExps // Make RegExps say that they are RegExps
if (isRegExp(value)) { if (isRegExp(value)) {
base = ' ' + RegExp.prototype.toString.call(value); base = " " + RegExp.prototype.toString.call(value);
} }
// Make dates with properties first say the date // Make dates with properties first say the date
if (isDate(value)) { if (isDate(value)) {
base = ' ' + Date.prototype.toUTCString.call(value); base = " " + Date.prototype.toUTCString.call(value);
} }
// Make error with message first say the error // Make error with message first say the error
if (isError(value)) { if (isError(value)) {
base = ' ' + formatError(value); base = " " + formatError(value);
} }
if (keys.length === 0 && (!array || value.length == 0)) { if (keys.length === 0 && (!array || value.length == 0)) {
return braces[0] + base + braces[1]; return braces[0] + base + braces[1];
} }
if (recurseTimes < 0) { if (recurseTimes < 0) {
if (isRegExp(value)) { if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); return ctx.stylize(RegExp.prototype.toString.call(value), "regexp");
} else { } else {
return ctx.stylize('[Object]', 'special'); return ctx.stylize("[Object]", "special");
} }
} }
ctx.seen.push(value); ctx.seen.push(value);
var output; var output;
if (array) { if (array) {
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
} else { } else {
output = keys.map(function (key) { output = keys.map(function (key) {
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); return formatProperty(
}); ctx,
} value,
recurseTimes,
visibleKeys,
key,
array
);
});
}
ctx.seen.pop(); ctx.seen.pop();
return reduceToSingleString(output, base, braces); return reduceToSingleString(output, base, braces);
} }
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
var name, str, desc; var name, str, desc;
desc = { value: void 0 }; desc = { value: void 0 };
try { try {
// ie6 navigator.toString // ie6 navigator.toString
// throws Error: Object doesn't support this property or method // throws Error: Object doesn't support this property or method
desc.value = value[key]; desc.value = value[key];
} catch (e) { } catch (e) {
// ignore // ignore
} }
try { try {
// ie10 Object.getOwnPropertyDescriptor(window.location, 'hash') // ie10 Object.getOwnPropertyDescriptor(window.location, 'hash')
// throws TypeError: Object doesn't support this action // throws TypeError: Object doesn't support this action
if (Object.getOwnPropertyDescriptor) { if (Object.getOwnPropertyDescriptor) {
desc = Object.getOwnPropertyDescriptor(value, key) || desc; desc = Object.getOwnPropertyDescriptor(value, key) || desc;
} }
} catch (e) { } catch (e) {
// ignore // ignore
} }
if (desc.get) { if (desc.get) {
if (desc.set) { if (desc.set) {
str = ctx.stylize('[Getter/Setter]', 'special'); str = ctx.stylize("[Getter/Setter]", "special");
} else { } else {
str = ctx.stylize('[Getter]', 'special'); str = ctx.stylize("[Getter]", "special");
} }
} else { } else {
if (desc.set) { if (desc.set) {
str = ctx.stylize('[Setter]', 'special'); str = ctx.stylize("[Setter]", "special");
} }
} }
if (!hasOwn(visibleKeys, key)) { if (!hasOwn(visibleKeys, key)) {
name = '[' + key + ']'; name = "[" + key + "]";
} }
if (!str) { if (!str) {
if (ctx.seen.indexOf(desc.value) < 0) { if (ctx.seen.indexOf(desc.value) < 0) {
if (isNull(recurseTimes)) { if (isNull(recurseTimes)) {
str = formatValue(ctx, desc.value, null); str = formatValue(ctx, desc.value, null);
} else {
str = formatValue(ctx, desc.value, recurseTimes - 1);
}
if (str.indexOf("\n") > -1) {
if (array) {
str = str
.split("\n")
.map(function (line) {
return " " + line;
})
.join("\n")
.substr(2);
} else { } else {
str = formatValue(ctx, desc.value, recurseTimes - 1); str =
"\n" +
str
.split("\n")
.map(function (line) {
return " " + line;
})
.join("\n");
} }
if (str.indexOf('\n') > -1) { }
if (array) { } else {
str = str.split('\n').map(function (line) { str = ctx.stylize("[Circular]", "special");
return ' ' + line; }
}).join('\n').substr(2); }
} else { if (isUndefined(name)) {
str = '\n' + str.split('\n').map(function (line) { if (array && key.match(/^\d+$/)) {
return ' ' + line; return str;
}).join('\n'); }
} name = JSON.stringify("" + key);
} if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
} else { name = name.substr(1, name.length - 2);
str = ctx.stylize('[Circular]', 'special'); name = ctx.stylize(name, "name");
} } else {
} name = name
if (isUndefined(name)) { .replace(/'/g, "\\'")
if (array && key.match(/^\d+$/)) { .replace(/\\"/g, '"')
return str; .replace(/(^"|"$)/g, "'");
} name = ctx.stylize(name, "string");
name = JSON.stringify('' + key); }
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { }
name = name.substr(1, name.length - 2);
name = ctx.stylize(name, 'name');
} else {
name = name.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'");
name = ctx.stylize(name, 'string');
}
}
return name + ': ' + str; return name + ": " + str;
} }
function formatPrimitive(ctx, value) { function formatPrimitive(ctx, value) {
if (isUndefined(value)) if (isUndefined(value)) return ctx.stylize("undefined", "undefined");
return ctx.stylize('undefined', 'undefined'); if (isString(value)) {
if (isString(value)) { var simple =
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') "'" +
JSON.stringify(value)
.replace(/^"|"$/g, "")
.replace(/'/g, "\\'") .replace(/'/g, "\\'")
.replace(/\\"/g, '"') + '\''; .replace(/\\"/g, '"') +
return ctx.stylize(simple, 'string'); "'";
} return ctx.stylize(simple, "string");
if (isNumber(value)) }
return ctx.stylize('' + value, 'number'); if (isNumber(value)) return ctx.stylize("" + value, "number");
if (isBoolean(value)) if (isBoolean(value)) return ctx.stylize("" + value, "boolean");
return ctx.stylize('' + value, 'boolean'); // For some reason typeof null is "object", so special case here.
// For some reason typeof null is "object", so special case here. if (isNull(value)) return ctx.stylize("null", "null");
if (isNull(value))
return ctx.stylize('null', 'null');
} }
function reduceToSingleString(output, base, braces) { function reduceToSingleString(output, base, braces) {
var numLinesEst = 0; var numLinesEst = 0;
var length = output.reduce(function (prev, cur) { var length = output.reduce(function (prev, cur) {
numLinesEst++; numLinesEst++;
if (cur.indexOf('\n') >= 0) numLinesEst++; if (cur.indexOf("\n") >= 0) numLinesEst++;
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; return prev + cur.replace(/\u001b\[\d\d?m/g, "").length + 1;
}, 0); }, 0);
if (length > 60) { if (length > 60) {
return braces[0] + return (
(base === '' ? '' : base + '\n ') + braces[0] +
' ' + (base === "" ? "" : base + "\n ") +
output.join(',\n ') + " " +
' ' + output.join(",\n ") +
braces[1]; " " +
} braces[1]
);
}
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; return braces[0] + base + " " + output.join(", ") + " " + braces[1];
} }
function _extend<T extends Y, Y>(origin: T, add: Y) { function _extend<T extends Y, Y>(origin: T, add: Y) {
// Don't do anything if add isn't an object // Don't do anything if add isn't an object
if (!add || !isObject(add)) return origin; if (!add || !isObject(add)) return origin;
var keys = Object.keys(add); var keys = Object.keys(add);
var i = keys.length; var i = keys.length;
while (i--) { while (i--) {
origin[keys[i]] = add[keys[i]]; origin[keys[i]] = add[keys[i]];
} }
return origin; return origin;
} }

View File

@ -1,20 +1,19 @@
import { Logging, LoggingBase, LoggingTypes, Colors, withColor } from "."; import Logging from "./index.js";
import { LoggingBase, LoggingTypes, Format } 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);
@ -23,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");
@ -47,7 +46,27 @@ 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");
const timer = Logging.time("timer1", "Test Timer"); Logging.logLevel = LoggingTypes.Debug;
const c1 = Logging.getChild("child-level-1");
c1.log("Hello from Child 1");
const c2 = c1.getChild("child-level-2");
c2.log("Hello from Child 2");
c2.log("MSG from C2");
c1.log("MSG from C1");
Logging.log("MSG from root");
const timer = Logging.time("timer1");
setTimeout(() => timer.end(), 1000); setTimeout(() => timer.end(), 1000);
const withoutFile = new LoggingBase({
resolve_filename: false,
});
withoutFile.log("This should not have a file attached");

View File

@ -1,119 +1,256 @@
import { ObservableInterface } from "@hibas123/utils";
export enum LoggingTypes { export enum LoggingTypes {
Debug, Debug,
Log, Log,
Warning, Warn,
Error Error,
} }
export const TerminalFormats = { export const TerminalFormats = {
Reset: "\x1b[0m", Reset: "\x1b[0m",
Bold: "\x1b[1m", Bold: "\x1b[1m",
Underscore: "\x1b[4m", Underscore: "\x1b[4m",
Blink: "\x1b[5m", Blink: "\x1b[5m",
Reverse: "\x1b[7m", Reverse: "\x1b[7m",
Hidden: "\x1b[8m", Hidden: "\x1b[8m",
FgBlack: "\x1b[30m", FgBlack: "\x1b[30m",
FgRed: "\x1b[31m", FgRed: "\x1b[31m",
FgGreen: "\x1b[32m", FgGreen: "\x1b[32m",
FgYellow: "\x1b[33m", FgYellow: "\x1b[33m",
FgBlue: "\x1b[34m", FgBlue: "\x1b[34m",
FgMagenta: "\x1b[35m", FgMagenta: "\x1b[35m",
FgCyan: "\x1b[36m", FgCyan: "\x1b[36m",
FgWhite: "\x1b[37m", FgWhite: "\x1b[37m",
BgBlack: "\x1b[40m", BgBlack: "\x1b[40m",
BgRed: "\x1b[41m", BgRed: "\x1b[41m",
BgGreen: "\x1b[42m", BgGreen: "\x1b[42m",
BgYellow: "\x1b[43m", BgYellow: "\x1b[43m",
BgBlue: "\x1b[44m", BgBlue: "\x1b[44m",
BgMagenta: "\x1b[45m", BgMagenta: "\x1b[45m",
BgCyan: "\x1b[46m", BgCyan: "\x1b[46m",
BgWhite: "\x1b[47m" BgWhite: "\x1b[47m",
}
export enum FormatTypes {
COLOR,
BOLD,
UNDERSCORE,
BLINK
}
export enum Colors {
NONE,
RED,
GREEN,
YELLOW,
BLUE,
MAGENTA,
CYAN,
WHITE
}
export interface FormatConfig {
error: Format[];
warning: Format[];
log: Format[];
debug: Format[];
date: Format[];
file: Format[];
}
function colorFormat(color: Colors) {
return {
type: FormatTypes.COLOR,
color
}
}
const boldFormat = {
type: FormatTypes.BOLD
}; };
export enum IColors {
NONE,
RED,
GREEN,
YELLOW,
BLUE,
MAGENTA,
CYAN,
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 {
error: Formatted;
warning: Formatted;
log: Formatted;
debug: Formatted;
date: Formatted;
file: Formatted;
names: Formatted;
names_delimiter: Formatted;
}
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.CYAN);
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[], date: Date;
formatted: FormattedLine[] file: string;
};
date: Date;
file: string;
} }
export interface Adapter { export interface Adapter {
init(observable: ObservableInterface<Message>, name?: string): void | Promise<void>; readonly level: LoggingTypes;
flush(sync: true): void; /**
flush(sync: false): void | Promise<void>; * This function initialises the Adapter. It might be called multiple times, when added to multiple instances
* @param observable An observable to subscribe to messages
*/
init(): void | Promise<void>;
flush(sync: true): void;
flush(sync: false): void | Promise<void>;
close?(): void; setLevel(level: LoggingTypes): void;
onMessage(message: Message): void;
/**
* When a close function is available, it will be called when no logging instance references it anymore.
*
* WARNING: The adapter might be reinitialised, when it is added to a new Logging instance
*/
close?(): void;
}
export interface ILoggingTimer {
end: () => number;
}
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;
} }

View File

@ -1,19 +1,13 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "ESNext",
"target": "es2017", "target": "ESNext",
"moduleResolution": "node",
"outDir": "esm",
"noImplicitAny": false, "noImplicitAny": false,
"sourceMap": true, "sourceMap": true,
"outDir": "out", "declaration": true
"declaration": true,
"typeRoots": [
"node_modules/@types"
]
}, },
"exclude": [ "exclude": ["node_modules"],
"node_modules" "include": ["src"]
],
"include": [
"src"
]
} }