Compare commits
30 Commits
facb7e7b40
...
master
Author | SHA1 | Date | |
---|---|---|---|
3de5f368ef | |||
e0b51625d8 | |||
153aca0ccb | |||
7ca0c4fd72 | |||
8e183ac1a5 | |||
feed4626e6 | |||
9d4e521619 | |||
7d75f65dd3 | |||
eeed068ddd | |||
6daf815ea8 | |||
357b98c69a | |||
96d7808f35 | |||
176d37249d | |||
bcff79fc90 | |||
b92caf6468 | |||
f34800d725 | |||
1fd8da459b | |||
4420fb13ea | |||
511bdf127f | |||
7ae1c3e16e | |||
ca896c1c34 | |||
30f5e241ae | |||
a1cd860688 | |||
f51f4e2aad | |||
fa7a168f17 | |||
2eb9356a9d | |||
9182efe7e7 | |||
94d9731cdd | |||
b647b8fae6 | |||
ce9742a20e |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@ node_modules/
|
||||
logs/
|
||||
yarn.lock
|
||||
out/
|
||||
.history/
|
||||
esm/
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal 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"]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,29 +1,29 @@
|
||||
# Logging
|
||||
|
||||
Simple logging module, that supports terminal coloring and different plugins
|
||||
|
||||
# Getting Started
|
||||
## Getting Started
|
||||
|
||||
``` javascript
|
||||
|
||||
const Logging = require("@hibas123/logging").Logging;
|
||||
```javascript
|
||||
const Logging = require("@hibas123/logging").default;
|
||||
|
||||
Logging.log("Hello there");
|
||||
|
||||
```
|
||||
|
||||
There are different Logging levels, that also apply terminal coloring:
|
||||
|
||||
``` javascript
|
||||
Logging.debug("Debug message")
|
||||
Logging.log("Log message")
|
||||
Logging.warning("Warning")
|
||||
Logging.error(new Error("To less creativity"))
|
||||
Logging.error("Just an simple message as error")
|
||||
Logging.errorMessage("Nearly the same as error")
|
||||
```javascript
|
||||
Logging.debug("Debug message");
|
||||
Logging.log("Log message");
|
||||
Logging.warning("Warning");
|
||||
Logging.error(new Error("To less creativity"));
|
||||
Logging.error("Just an simple message 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.
|
||||
|
||||
@ -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.
|
||||
|
||||
``` javascript
|
||||
const CustomLogging = new LoggingBase(name | {
|
||||
name: "custom", // default undefined
|
||||
console: false // default true
|
||||
});
|
||||
```javascript
|
||||
const CustomLogging = new LoggingBase(
|
||||
name |
|
||||
{
|
||||
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.
|
||||
|
||||
``` javascript
|
||||
```javascript
|
||||
const Demo = new LoggingBase("Demo");
|
||||
Demo.addAdapter(new DemoAdapter({ color: "rainbow" }));
|
||||
```
|
||||
|
||||
The adapters need to provide a very simple Interface:
|
||||
|
||||
``` typescript
|
||||
```typescript
|
||||
interface Adapter {
|
||||
init(observable: ObservableInterface<Message>, name?: string): void | Promise<void>;
|
||||
init(): void | Promise<void>;
|
||||
|
||||
flush(sync: true): void;
|
||||
flush(sync: false): void | Promise<void>;
|
||||
onMessage(message: Message): void;
|
||||
|
||||
flush(sync: true): void;
|
||||
flush(sync: false): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface Message {
|
||||
type: LoggingTypes;
|
||||
name?:string;
|
||||
text: {
|
||||
raw: string[],
|
||||
formatted: string[]
|
||||
};
|
||||
date: Date;
|
||||
file: string;
|
||||
type: LoggingTypes;
|
||||
names?: string[];
|
||||
text: IFormatted;
|
||||
date: Date;
|
||||
file: string;
|
||||
}
|
||||
|
||||
export interface IFormatted {
|
||||
color?: IColors;
|
||||
bgcolor?: IColors;
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
blink?: boolean;
|
||||
underscore?: boolean;
|
||||
|
||||
content: string;
|
||||
}
|
||||
|
||||
enum LoggingTypes {
|
||||
Log,
|
||||
Warning,
|
||||
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
|
||||
|
||||
Copyright (c) 2018 Fabian Stamm
|
||||
@ -90,4 +99,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
54
benchmark.js
Normal file
54
benchmark.js
Normal 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
8
deno_build.js
Normal 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
16
meta.json
Normal 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
5925
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "@hibas123/logging",
|
||||
"version": "2.2.2",
|
||||
"version": "4.0.1",
|
||||
"description": "",
|
||||
"main": "out/index.js",
|
||||
"types": "out/index.d.ts",
|
||||
"type": "module",
|
||||
"main": "esm/index.js",
|
||||
"types": "esm/index.d.ts",
|
||||
"module": "esm/index.js",
|
||||
"scripts": {
|
||||
"prepublish": "tsc",
|
||||
"prepublishOnly": "npm run build",
|
||||
"build": "tsc",
|
||||
"watch-ts": "tsc --watch",
|
||||
"watch-js": "nodemon out/test.js",
|
||||
"watch": "concurrently npm:watch-*",
|
||||
"test": "node out/test.js"
|
||||
"test": "tsc && node esm/test.js",
|
||||
"postpublish": "denreg publish",
|
||||
"bench": "tsc && node benchmark.js",
|
||||
"prof": "tsc && ndb --prof benchmark.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -20,16 +22,15 @@
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src/",
|
||||
"out/",
|
||||
"esm/",
|
||||
"tsconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"concurrently": "^5.1.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hibas123/utils": "^2.2.3"
|
||||
"concurrently": "^6.0.2",
|
||||
"ndb": "^1.1.5",
|
||||
"nodemon": "^2.0.7",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
}
|
||||
}
|
669
src/base.ts
669
src/base.ts
@ -1,37 +1,19 @@
|
||||
import { Observable } from "@hibas123/utils";
|
||||
import { ConsoleAdapter } from "./consolewriter";
|
||||
import inspect from "./inspect";
|
||||
import { ConsoleAdapter } from "./consolewriter.js";
|
||||
import inspect from "./inspect.js";
|
||||
import {
|
||||
Adapter,
|
||||
LoggingTypes,
|
||||
Message,
|
||||
FormatConfig,
|
||||
DefaultFormatConfig,
|
||||
Format,
|
||||
FormatTypes,
|
||||
Colors,
|
||||
FormattedText,
|
||||
FormattedLine,
|
||||
} from "./types";
|
||||
import Logging from ".";
|
||||
FormatConfig,
|
||||
Formatted,
|
||||
ILoggingInterface,
|
||||
LoggingTypes,
|
||||
Message,
|
||||
} from "./types.js";
|
||||
|
||||
const browser = typeof window !== "undefined";
|
||||
|
||||
export function removeColors(text: string) {
|
||||
text = text.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
""
|
||||
);
|
||||
|
||||
// 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 ILoggingOptions {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
console: boolean;
|
||||
|
||||
/**
|
||||
* Enables printing of calling file
|
||||
*/
|
||||
resolve_filename: boolean;
|
||||
}
|
||||
|
||||
export class LoggingBase {
|
||||
private _formatMap: FormatConfig = new DefaultFormatConfig();
|
||||
export interface INativeFunctions {
|
||||
startTimer(): any;
|
||||
endTimer(start: any): number;
|
||||
}
|
||||
|
||||
public set formatMap(value: FormatConfig) {
|
||||
this._formatMap = value;
|
||||
}
|
||||
|
||||
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 {
|
||||
export const DefaultNativeFunctions = {
|
||||
startTimer: () => {
|
||||
if (browser && window.performance && window.performance.now) {
|
||||
return window.performance.now();
|
||||
} else {
|
||||
return Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The time difference in milliseconds (fractions allowed!)
|
||||
* @param start Start time from getCurrentTime
|
||||
*/
|
||||
protected getTimeDiff(start: any) {
|
||||
},
|
||||
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();
|
||||
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) {
|
||||
if (!id) {
|
||||
id = Math.floor(Math.random() * 899999 + 100000).toString();
|
||||
}
|
||||
protected abstract message(
|
||||
type: LoggingTypes,
|
||||
names: string[],
|
||||
message: any[],
|
||||
caller?: { file: string; line: number; column?: number }
|
||||
): void;
|
||||
|
||||
this.timerMap.set(id, {
|
||||
name,
|
||||
start: this.getCurrentTime(),
|
||||
constructor(names: string[]) {
|
||||
this.#names = names;
|
||||
|
||||
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 {
|
||||
@ -217,167 +135,278 @@ export class LoggingBase {
|
||||
}
|
||||
|
||||
timeEnd(id: string) {
|
||||
let timer = this.timerMap.get(id);
|
||||
let timer = this.#timerMap.get(id);
|
||||
if (timer) {
|
||||
let diff = this.getTimeDiff(timer.start);
|
||||
Logging.message(LoggingTypes.Debug, [
|
||||
withColor(Colors.GREEN, `[${timer.name}]`),
|
||||
let diff = LoggingBase.nativeFunctions.endTimer(timer.start);
|
||||
|
||||
this.message(LoggingTypes.Debug, this.#names, [
|
||||
Format.green(`[${timer.name}]`),
|
||||
`->`,
|
||||
withColor(Colors.BLUE, diff.toFixed(4)),
|
||||
Format.blue(diff.toFixed(4)),
|
||||
"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(
|
||||
type: LoggingTypes,
|
||||
message: any[],
|
||||
caller?: { file: string; line: number }
|
||||
) {
|
||||
let date = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "");
|
||||
async addAdapter(adapter: Adapter) {
|
||||
const init = adapter.init();
|
||||
|
||||
let file_raw = caller || getCallerFile();
|
||||
let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`;
|
||||
const add = () => {
|
||||
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_format: Format[] = [];
|
||||
let type_format: Formatted;
|
||||
switch (type) {
|
||||
case LoggingTypes.Log:
|
||||
type_format = this._formatMap.log;
|
||||
type_format = this.#format_map.log;
|
||||
break;
|
||||
case LoggingTypes.Error:
|
||||
type_format = this._formatMap.error;
|
||||
type_format = this.#format_map.error;
|
||||
break;
|
||||
case LoggingTypes.Debug:
|
||||
type_format = this._formatMap.debug;
|
||||
type_format = this.#format_map.debug;
|
||||
break;
|
||||
case LoggingTypes.Warning:
|
||||
type_format = this._formatMap.warning;
|
||||
case LoggingTypes.Warn:
|
||||
type_format = this.#format_map.warning;
|
||||
break;
|
||||
}
|
||||
|
||||
const prefix: FormattedText[] = [];
|
||||
const a = (text: string, formats: Format[] = []) => {
|
||||
prefix.push({
|
||||
text,
|
||||
formats,
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
const nameFormatted: Formatted<string>[] = [];
|
||||
if (names.length > 0) {
|
||||
nameFormatted.push(new Formatted("["));
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
nameFormatted.push(new Formatted(names[i], this.#format_map.names));
|
||||
if (i < names.length - 1) {
|
||||
nameFormatted.push(this.#format_map.names_delimiter);
|
||||
}
|
||||
if (typeof e !== "string")
|
||||
e = inspect(e, {
|
||||
colors: true,
|
||||
showHidden: true,
|
||||
depth: 3,
|
||||
}) as string;
|
||||
}
|
||||
nameFormatted.push(new Formatted("]"));
|
||||
}
|
||||
|
||||
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 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)
|
||||
.split("\n")
|
||||
.map((text, index, { length }) => {
|
||||
line.push({ text, formats });
|
||||
if (index < length - 1) {
|
||||
newLine();
|
||||
}
|
||||
});
|
||||
|
||||
if (!e.endsWith("\n") && i < message.length - 1) {
|
||||
line.push({ text: " ", formats: [] });
|
||||
if (typeof msg !== "string") {
|
||||
msg = inspect(msg, {
|
||||
colors: false, //TODO: Maybe change when changing the removeColors to return formatted text?
|
||||
showHidden: true,
|
||||
depth: 3,
|
||||
}) as string;
|
||||
}
|
||||
|
||||
// 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 = {
|
||||
date: new Date(),
|
||||
date,
|
||||
file,
|
||||
name: this._name,
|
||||
text: {
|
||||
raw,
|
||||
formatted,
|
||||
},
|
||||
names,
|
||||
text: formattedMessage,
|
||||
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 {
|
||||
[colorSymbol]: Colors;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export function withColor(color: Colors, value: any): ColorFormat {
|
||||
return {
|
||||
[colorSymbol]: color,
|
||||
value,
|
||||
};
|
||||
for (const adapter of this.#adapters) {
|
||||
const cnt = LoggingBase[InitialisedAdapters].get(adapter);
|
||||
if (!cnt) {
|
||||
//TODO: should not happen!
|
||||
} else {
|
||||
if (cnt <= 1) {
|
||||
if (adapter.close) await adapter.close();
|
||||
LoggingBase[InitialisedAdapters].delete(adapter);
|
||||
} else {
|
||||
LoggingBase[InitialisedAdapters].set(adapter, cnt - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getStack() {
|
||||
// Save original 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;
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
// 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();
|
||||
} finally {
|
||||
// Restore original `Error.prepareStackTrace`
|
||||
(<any>Error).prepareStackTrace = origPrepareStackTrace;
|
||||
}
|
||||
}
|
||||
|
||||
function getCallerFile() {
|
||||
@ -388,29 +417,57 @@ function getCallerFile() {
|
||||
|
||||
while (stack.length) {
|
||||
let caller_file = stack.shift();
|
||||
if (current_file !== caller_file.getFileName())
|
||||
if (current_file !== caller_file.getFileName()) {
|
||||
return {
|
||||
file: baseName(caller_file.getFileName()),
|
||||
file: caller_file.getFileName(),
|
||||
line: caller_file.getLineNumber(),
|
||||
column: caller_file.getColumnNumber(),
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
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 };
|
||||
let lines = err.stack.split("\n");
|
||||
lines.shift(); // removing first line
|
||||
while (lines.length > 0) {
|
||||
let line = lines.shift();
|
||||
let matches = line.match(/[a-zA-Z_-]+[.][a-zA-Z_-]+[:][0-9]+/g);
|
||||
let matches = line.match(
|
||||
/[<]?([a-zA-Z]:)?([\/\\]?[a-zA-Z_-])+[.>][a-zA-Z_-]*([:][0-9]+)+/g
|
||||
);
|
||||
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 {
|
||||
file: f,
|
||||
line: Number(line),
|
||||
file,
|
||||
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;
|
||||
}
|
||||
|
@ -1,22 +1,30 @@
|
||||
import { ObservableInterface } from "@hibas123/utils";
|
||||
import { Colors } from "./index";
|
||||
import { Colors } from "./index.js";
|
||||
import {
|
||||
Adapter,
|
||||
Message,
|
||||
FormattedLine,
|
||||
TerminalFormats,
|
||||
FormatTypes,
|
||||
} from "./types";
|
||||
Formatted,
|
||||
IFormatted,
|
||||
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 {
|
||||
init(observable: ObservableInterface<Message>) {
|
||||
observable.subscribe(this.onMessage.bind(this));
|
||||
}
|
||||
level: LoggingTypes = LoggingTypes.Debug;
|
||||
constructor(private colors: boolean = true) {}
|
||||
|
||||
init() {}
|
||||
flush() {}
|
||||
|
||||
setLevel(level: LoggingTypes) {
|
||||
this.level = level;
|
||||
}
|
||||
// TODO: Check if required!
|
||||
// private escape(text: string): string {
|
||||
// return text
|
||||
@ -24,102 +32,161 @@ export class ConsoleAdapter implements Adapter {
|
||||
// .replace(/%c/g, "%%c")
|
||||
// }
|
||||
|
||||
private formatLine(line: FormattedLine): [string, string[] | undefined] {
|
||||
private format(
|
||||
formatted: IFormatted<string>[]
|
||||
): [string, string[] | undefined] {
|
||||
let text = "";
|
||||
let style_formats: string[] = [];
|
||||
|
||||
if (!browser) {
|
||||
for (let part of line) {
|
||||
// NodeJS or Deno
|
||||
for (const format of formatted) {
|
||||
let formats = "";
|
||||
for (let format of part.formats) {
|
||||
switch (format.type) {
|
||||
case FormatTypes.BOLD:
|
||||
formats += TerminalFormats.Bold;
|
||||
if (format.bold) {
|
||||
formats += TerminalFormats.Bold;
|
||||
}
|
||||
|
||||
if (format.blink) {
|
||||
formats += TerminalFormats.Blink;
|
||||
}
|
||||
|
||||
if (format.underscore) {
|
||||
formats += TerminalFormats.Underscore;
|
||||
}
|
||||
|
||||
if (format.color) {
|
||||
switch (format.color) {
|
||||
case Colors.RED:
|
||||
formats += TerminalFormats.FgRed;
|
||||
break;
|
||||
case FormatTypes.UNDERSCORE:
|
||||
formats += TerminalFormats.Underscore;
|
||||
case Colors.GREEN:
|
||||
formats += TerminalFormats.FgGreen;
|
||||
break;
|
||||
case FormatTypes.BLINK:
|
||||
formats += TerminalFormats.Blink;
|
||||
case Colors.YELLOW:
|
||||
formats += TerminalFormats.FgYellow;
|
||||
break;
|
||||
case FormatTypes.COLOR:
|
||||
switch (format.color) {
|
||||
case Colors.RED:
|
||||
formats += TerminalFormats.FgRed;
|
||||
break;
|
||||
case Colors.GREEN:
|
||||
formats += TerminalFormats.FgGreen;
|
||||
break;
|
||||
case Colors.YELLOW:
|
||||
formats += TerminalFormats.FgYellow;
|
||||
break;
|
||||
case Colors.BLUE:
|
||||
formats += TerminalFormats.FgBlue;
|
||||
break;
|
||||
case Colors.MAGENTA:
|
||||
formats += TerminalFormats.FgMagenta;
|
||||
break;
|
||||
case Colors.CYAN:
|
||||
formats += TerminalFormats.FgCyan;
|
||||
break;
|
||||
case Colors.WHITE:
|
||||
formats += TerminalFormats.FgWhite;
|
||||
break;
|
||||
}
|
||||
case Colors.BLUE:
|
||||
formats += TerminalFormats.FgBlue;
|
||||
break;
|
||||
case Colors.MAGENTA:
|
||||
formats += TerminalFormats.FgMagenta;
|
||||
break;
|
||||
case Colors.CYAN:
|
||||
formats += TerminalFormats.FgCyan;
|
||||
break;
|
||||
case Colors.WHITE:
|
||||
formats += TerminalFormats.FgWhite;
|
||||
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 (let part of line) {
|
||||
for (const format of formatted) {
|
||||
let styles: string[] = [];
|
||||
let resetStyles: string[] = [];
|
||||
for (let format of part.formats) {
|
||||
switch (format.type) {
|
||||
case FormatTypes.BOLD:
|
||||
styles.push("font-weight: bold;");
|
||||
resetStyles.push("font-weight: unset");
|
||||
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 FormatTypes.UNDERSCORE:
|
||||
styles.push("text-decoration: underline");
|
||||
resetStyles.push("text-decoration: unset");
|
||||
case Colors.GREEN:
|
||||
color = "green";
|
||||
break;
|
||||
case FormatTypes.BLINK:
|
||||
styles.push("text-decoration: blink");
|
||||
resetStyles.push("text-decoration: unset");
|
||||
case Colors.YELLOW:
|
||||
color = "gold";
|
||||
break;
|
||||
case FormatTypes.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");
|
||||
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");
|
||||
}
|
||||
text += "%c" + part.text.replace(/%c/g, "%%c") + "%c";
|
||||
|
||||
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(";"));
|
||||
}
|
||||
}
|
||||
@ -128,27 +195,27 @@ export class ConsoleAdapter implements Adapter {
|
||||
}
|
||||
|
||||
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 t;
|
||||
})
|
||||
.join("\n");
|
||||
// console.log(formats);
|
||||
console.log(text, ...formats);
|
||||
if (this.colors) {
|
||||
const [text, formats] = this.format(message.text);
|
||||
console.log(text, ...formats);
|
||||
} else {
|
||||
console.log(Formatted.strip(message.text));
|
||||
}
|
||||
} else {
|
||||
lines.forEach((line) => {
|
||||
let [text] = this.formatLine(line);
|
||||
console.log(prefix + text);
|
||||
});
|
||||
const text = this.colors
|
||||
? this.format(message.text)[0]
|
||||
: Formatted.strip(message.text);
|
||||
|
||||
if (deno) {
|
||||
//TODO: Deno specific thing
|
||||
console.log(text);
|
||||
} else if (typeof process !== "undefined") {
|
||||
//NodeJS
|
||||
process.stdout.write(text + "\n");
|
||||
} else {
|
||||
console.log(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
src/index.ts
37
src/index.ts
@ -1,22 +1,23 @@
|
||||
import { LoggingBase } from "./base";
|
||||
export { ConsoleAdapter } from "./consolewriter";
|
||||
export { LoggingBase, LoggingBaseOptions, removeColors, withColor } from "./base";
|
||||
import { LoggingBase } from "./base.js";
|
||||
export { LoggingBase } from "./base.js";
|
||||
export { ConsoleAdapter } from "./consolewriter.js";
|
||||
export { ILoggingOptions, INativeFunctions } from "./base.js";
|
||||
|
||||
export {
|
||||
Adapter,
|
||||
LoggingTypes,
|
||||
Message,
|
||||
FormatConfig,
|
||||
FormattedLine,
|
||||
DefaultFormatConfig as DefaultColorMap,
|
||||
FormattedText,
|
||||
Colors,
|
||||
Format,
|
||||
FormatTypes,
|
||||
TerminalFormats
|
||||
} from "./types";
|
||||
Adapter,
|
||||
LoggingTypes,
|
||||
Message,
|
||||
FormatConfig,
|
||||
DefaultFormatConfig as DefaultColorMap,
|
||||
IColors as Colors,
|
||||
Format,
|
||||
TerminalFormats,
|
||||
Formatted,
|
||||
IFormatted,
|
||||
ILoggingInterface,
|
||||
ILoggingTimer,
|
||||
} from "./types.js";
|
||||
|
||||
export { ObservableInterface } from "@hibas123/utils";
|
||||
const Logging = new LoggingBase();
|
||||
|
||||
export let Logging: LoggingBase = undefined;
|
||||
Logging = new LoggingBase();
|
||||
export default Logging;
|
||||
|
636
src/inspect.ts
636
src/inspect.ts
@ -1,12 +1,11 @@
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
interface InspectOptions {
|
||||
depth: number;
|
||||
colors: boolean;
|
||||
showHidden: boolean;
|
||||
depth: number;
|
||||
colors: boolean;
|
||||
showHidden: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,397 +18,440 @@ interface InspectOptions {
|
||||
*/
|
||||
/* legacy: obj, showHidden, depth, colors*/
|
||||
export default function inspect(obj: any, opts: Partial<InspectOptions>) {
|
||||
// default options
|
||||
let ctx = {
|
||||
seen: [],
|
||||
stylize: stylizeNoColor,
|
||||
depth: undefined,
|
||||
colors: undefined,
|
||||
showHidden: undefined,
|
||||
customInspect: undefined
|
||||
};
|
||||
// legacy...
|
||||
if (arguments.length >= 3) ctx.depth = arguments[2];
|
||||
if (arguments.length >= 4) ctx.colors = arguments[3];
|
||||
if (isBoolean(opts)) {
|
||||
// legacy...
|
||||
ctx.showHidden = opts;
|
||||
} else if (opts) {
|
||||
// got an "options" object
|
||||
_extend(ctx, opts);
|
||||
}
|
||||
// set default options
|
||||
if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
|
||||
if (isUndefined(ctx.depth)) ctx.depth = 2;
|
||||
if (isUndefined(ctx.colors)) ctx.colors = false;
|
||||
if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
|
||||
if (ctx.colors) ctx.stylize = stylizeWithColor;
|
||||
return formatValue(ctx, obj, ctx.depth);
|
||||
// default options
|
||||
let ctx = {
|
||||
seen: [],
|
||||
stylize: stylizeNoColor,
|
||||
depth: undefined,
|
||||
colors: undefined,
|
||||
showHidden: undefined,
|
||||
customInspect: undefined,
|
||||
};
|
||||
// legacy...
|
||||
if (arguments.length >= 3) ctx.depth = arguments[2];
|
||||
if (arguments.length >= 4) ctx.colors = arguments[3];
|
||||
if (isBoolean(opts)) {
|
||||
// legacy...
|
||||
ctx.showHidden = opts;
|
||||
} else if (opts) {
|
||||
// got an "options" object
|
||||
_extend(ctx, opts);
|
||||
}
|
||||
// set default options
|
||||
if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
|
||||
if (isUndefined(ctx.depth)) ctx.depth = 2;
|
||||
if (isUndefined(ctx.colors)) ctx.colors = false;
|
||||
if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
|
||||
if (ctx.colors) ctx.stylize = stylizeWithColor;
|
||||
return formatValue(ctx, obj, ctx.depth);
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
|
||||
inspect.colors = {
|
||||
'bold': [1, 22],
|
||||
'italic': [3, 23],
|
||||
'underline': [4, 24],
|
||||
'inverse': [7, 27],
|
||||
'white': [37, 39],
|
||||
'grey': [90, 39],
|
||||
'black': [30, 39],
|
||||
'blue': [34, 39],
|
||||
'cyan': [36, 39],
|
||||
'green': [32, 39],
|
||||
'magenta': [35, 39],
|
||||
'red': [31, 39],
|
||||
'yellow': [33, 39]
|
||||
bold: [1, 22],
|
||||
italic: [3, 23],
|
||||
underline: [4, 24],
|
||||
inverse: [7, 27],
|
||||
white: [37, 39],
|
||||
grey: [90, 39],
|
||||
black: [30, 39],
|
||||
blue: [34, 39],
|
||||
cyan: [36, 39],
|
||||
green: [32, 39],
|
||||
magenta: [35, 39],
|
||||
red: [31, 39],
|
||||
yellow: [33, 39],
|
||||
};
|
||||
|
||||
// Don't use 'blue' not visible on cmd.exe
|
||||
inspect.styles = {
|
||||
'special': 'cyan',
|
||||
'number': 'yellow',
|
||||
'boolean': 'yellow',
|
||||
'undefined': 'grey',
|
||||
'null': 'bold',
|
||||
'string': 'green',
|
||||
'date': 'magenta',
|
||||
// "name": intentionally not styling
|
||||
'regexp': 'red'
|
||||
special: "cyan",
|
||||
number: "yellow",
|
||||
boolean: "yellow",
|
||||
undefined: "grey",
|
||||
null: "bold",
|
||||
string: "green",
|
||||
date: "magenta",
|
||||
// "name": intentionally not styling
|
||||
regexp: "red",
|
||||
};
|
||||
|
||||
function stylizeNoColor(str, styleType) {
|
||||
return str;
|
||||
return str;
|
||||
}
|
||||
|
||||
function isBoolean(arg) {
|
||||
return typeof arg === 'boolean';
|
||||
return typeof arg === "boolean";
|
||||
}
|
||||
|
||||
function isUndefined(arg) {
|
||||
return arg === void 0;
|
||||
return arg === void 0;
|
||||
}
|
||||
|
||||
function stylizeWithColor(str, styleType) {
|
||||
var style = inspect.styles[styleType];
|
||||
var style = inspect.styles[styleType];
|
||||
|
||||
if (style) {
|
||||
return '\u001b[' + inspect.colors[style][0] + 'm' + str +
|
||||
'\u001b[' + inspect.colors[style][1] + 'm';
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
if (style) {
|
||||
return (
|
||||
"\u001b[" +
|
||||
inspect.colors[style][0] +
|
||||
"m" +
|
||||
str +
|
||||
"\u001b[" +
|
||||
inspect.colors[style][1] +
|
||||
"m"
|
||||
);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
function isFunction(arg) {
|
||||
return typeof arg === 'function';
|
||||
return typeof arg === "function";
|
||||
}
|
||||
|
||||
function isString(arg) {
|
||||
return typeof arg === 'string';
|
||||
return typeof arg === "string";
|
||||
}
|
||||
|
||||
function isNumber(arg) {
|
||||
return typeof arg === 'number';
|
||||
return typeof arg === "number";
|
||||
}
|
||||
|
||||
function isNull(arg) {
|
||||
return arg === null;
|
||||
return arg === null;
|
||||
}
|
||||
|
||||
function hasOwn(obj, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
}
|
||||
|
||||
function isRegExp(re) {
|
||||
return isObject(re) && objectToString(re) === '[object RegExp]';
|
||||
return isObject(re) && objectToString(re) === "[object RegExp]";
|
||||
}
|
||||
|
||||
function isObject(arg) {
|
||||
return typeof arg === 'object' && arg !== null;
|
||||
return typeof arg === "object" && arg !== null;
|
||||
}
|
||||
|
||||
function isError(e) {
|
||||
return isObject(e) &&
|
||||
(objectToString(e) === '[object Error]' || e instanceof Error);
|
||||
return (
|
||||
isObject(e) &&
|
||||
(objectToString(e) === "[object Error]" || e instanceof Error)
|
||||
);
|
||||
}
|
||||
|
||||
function isDate(d) {
|
||||
return isObject(d) && objectToString(d) === '[object Date]';
|
||||
return isObject(d) && objectToString(d) === "[object Date]";
|
||||
}
|
||||
|
||||
function objectToString(o) {
|
||||
return Object.prototype.toString.call(o);
|
||||
return Object.prototype.toString.call(o);
|
||||
}
|
||||
|
||||
function arrayToHash(array) {
|
||||
var hash = {};
|
||||
var hash = {};
|
||||
|
||||
array.forEach(function (val, idx) {
|
||||
hash[val] = true;
|
||||
});
|
||||
array.forEach(function (val, idx) {
|
||||
hash[val] = true;
|
||||
});
|
||||
|
||||
return hash;
|
||||
return hash;
|
||||
}
|
||||
|
||||
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
|
||||
var output = [];
|
||||
for (var i = 0, l = value.length; i < l; ++i) {
|
||||
if (hasOwn(value, String(i))) {
|
||||
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
|
||||
String(i), true));
|
||||
} else {
|
||||
output.push('');
|
||||
}
|
||||
}
|
||||
keys.forEach(function (key) {
|
||||
if (!key.match(/^\d+$/)) {
|
||||
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
|
||||
key, true));
|
||||
}
|
||||
});
|
||||
return output;
|
||||
var output = [];
|
||||
for (var i = 0, l = value.length; i < l; ++i) {
|
||||
if (hasOwn(value, String(i))) {
|
||||
output.push(
|
||||
formatProperty(
|
||||
ctx,
|
||||
value,
|
||||
recurseTimes,
|
||||
visibleKeys,
|
||||
String(i),
|
||||
true
|
||||
)
|
||||
);
|
||||
} else {
|
||||
output.push("");
|
||||
}
|
||||
}
|
||||
keys.forEach(function (key) {
|
||||
if (!key.match(/^\d+$/)) {
|
||||
output.push(
|
||||
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)
|
||||
);
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function formatError(value) {
|
||||
return '[' + Error.prototype.toString.call(value) + ']';
|
||||
return "[" + Error.prototype.toString.call(value) + "]";
|
||||
}
|
||||
|
||||
function formatValue(ctx, value, recurseTimes) {
|
||||
// Provide a hook for user-specified inspect functions.
|
||||
// Check that value is an object with an inspect function on it
|
||||
if (ctx.customInspect &&
|
||||
value &&
|
||||
isFunction(value.inspect) &&
|
||||
// Filter out the util module, it's inspect function is special
|
||||
value.inspect !== inspect &&
|
||||
// Also filter out any prototype objects using the circular check.
|
||||
!(value.constructor && value.constructor.prototype === value)) {
|
||||
var ret = value.inspect(recurseTimes, ctx);
|
||||
if (!isString(ret)) {
|
||||
ret = formatValue(ctx, ret, recurseTimes);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// Provide a hook for user-specified inspect functions.
|
||||
// Check that value is an object with an inspect function on it
|
||||
if (
|
||||
ctx.customInspect &&
|
||||
value &&
|
||||
isFunction(value.inspect) &&
|
||||
// Filter out the util module, it's inspect function is special
|
||||
value.inspect !== inspect &&
|
||||
// Also filter out any prototype objects using the circular check.
|
||||
!(value.constructor && value.constructor.prototype === value)
|
||||
) {
|
||||
var ret = value.inspect(recurseTimes, ctx);
|
||||
if (!isString(ret)) {
|
||||
ret = formatValue(ctx, ret, recurseTimes);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Primitive types cannot have properties
|
||||
var primitive = formatPrimitive(ctx, value);
|
||||
if (primitive) {
|
||||
return primitive;
|
||||
}
|
||||
// Primitive types cannot have properties
|
||||
var primitive = formatPrimitive(ctx, value);
|
||||
if (primitive) {
|
||||
return primitive;
|
||||
}
|
||||
|
||||
// Look up the keys of the object.
|
||||
var keys = Object.keys(value);
|
||||
var visibleKeys = arrayToHash(keys);
|
||||
// Look up the keys of the object.
|
||||
var keys = Object.keys(value);
|
||||
var visibleKeys = arrayToHash(keys);
|
||||
|
||||
try {
|
||||
if (ctx.showHidden && Object.getOwnPropertyNames) {
|
||||
keys = Object.getOwnPropertyNames(value);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
if (ctx.showHidden && Object.getOwnPropertyNames) {
|
||||
keys = Object.getOwnPropertyNames(value);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// IE doesn't make error fields non-enumerable
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
|
||||
if (isError(value)
|
||||
&& (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
|
||||
return formatError(value);
|
||||
}
|
||||
// IE doesn't make error fields non-enumerable
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
|
||||
if (
|
||||
isError(value) &&
|
||||
(keys.indexOf("message") >= 0 || keys.indexOf("description") >= 0)
|
||||
) {
|
||||
return formatError(value);
|
||||
}
|
||||
|
||||
// Some type of object without properties can be shortcutted.
|
||||
if (keys.length === 0) {
|
||||
if (isFunction(value)) {
|
||||
var name = value.name ? ': ' + value.name : '';
|
||||
return ctx.stylize('[Function' + name + ']', 'special');
|
||||
}
|
||||
if (isRegExp(value)) {
|
||||
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
|
||||
}
|
||||
if (isDate(value)) {
|
||||
return ctx.stylize(Date.prototype.toString.call(value), 'date');
|
||||
}
|
||||
if (isError(value)) {
|
||||
return formatError(value);
|
||||
}
|
||||
}
|
||||
// Some type of object without properties can be shortcutted.
|
||||
if (keys.length === 0) {
|
||||
if (isFunction(value)) {
|
||||
var name = value.name ? ": " + value.name : "";
|
||||
return ctx.stylize("[Function" + name + "]", "special");
|
||||
}
|
||||
if (isRegExp(value)) {
|
||||
return ctx.stylize(RegExp.prototype.toString.call(value), "regexp");
|
||||
}
|
||||
if (isDate(value)) {
|
||||
return ctx.stylize(Date.prototype.toString.call(value), "date");
|
||||
}
|
||||
if (isError(value)) {
|
||||
return formatError(value);
|
||||
}
|
||||
}
|
||||
|
||||
var base = '', array = false, braces = ['{', '}'];
|
||||
var base = "",
|
||||
array = false,
|
||||
braces = ["{", "}"];
|
||||
|
||||
// Make Array say that they are Array
|
||||
if (Array.isArray(value)) {
|
||||
array = true;
|
||||
braces = ['[', ']'];
|
||||
}
|
||||
// Make Array say that they are Array
|
||||
if (Array.isArray(value)) {
|
||||
array = true;
|
||||
braces = ["[", "]"];
|
||||
}
|
||||
|
||||
// Make functions say that they are functions
|
||||
if (isFunction(value)) {
|
||||
var n = value.name ? ': ' + value.name : '';
|
||||
base = ' [Function' + n + ']';
|
||||
}
|
||||
// Make functions say that they are functions
|
||||
if (isFunction(value)) {
|
||||
var n = value.name ? ": " + value.name : "";
|
||||
base = " [Function" + n + "]";
|
||||
}
|
||||
|
||||
// Make RegExps say that they are RegExps
|
||||
if (isRegExp(value)) {
|
||||
base = ' ' + RegExp.prototype.toString.call(value);
|
||||
}
|
||||
// Make RegExps say that they are RegExps
|
||||
if (isRegExp(value)) {
|
||||
base = " " + RegExp.prototype.toString.call(value);
|
||||
}
|
||||
|
||||
// Make dates with properties first say the date
|
||||
if (isDate(value)) {
|
||||
base = ' ' + Date.prototype.toUTCString.call(value);
|
||||
}
|
||||
// Make dates with properties first say the date
|
||||
if (isDate(value)) {
|
||||
base = " " + Date.prototype.toUTCString.call(value);
|
||||
}
|
||||
|
||||
// Make error with message first say the error
|
||||
if (isError(value)) {
|
||||
base = ' ' + formatError(value);
|
||||
}
|
||||
// Make error with message first say the error
|
||||
if (isError(value)) {
|
||||
base = " " + formatError(value);
|
||||
}
|
||||
|
||||
if (keys.length === 0 && (!array || value.length == 0)) {
|
||||
return braces[0] + base + braces[1];
|
||||
}
|
||||
if (keys.length === 0 && (!array || value.length == 0)) {
|
||||
return braces[0] + base + braces[1];
|
||||
}
|
||||
|
||||
if (recurseTimes < 0) {
|
||||
if (isRegExp(value)) {
|
||||
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
|
||||
} else {
|
||||
return ctx.stylize('[Object]', 'special');
|
||||
}
|
||||
}
|
||||
if (recurseTimes < 0) {
|
||||
if (isRegExp(value)) {
|
||||
return ctx.stylize(RegExp.prototype.toString.call(value), "regexp");
|
||||
} else {
|
||||
return ctx.stylize("[Object]", "special");
|
||||
}
|
||||
}
|
||||
|
||||
ctx.seen.push(value);
|
||||
ctx.seen.push(value);
|
||||
|
||||
var output;
|
||||
if (array) {
|
||||
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
|
||||
} else {
|
||||
output = keys.map(function (key) {
|
||||
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
|
||||
});
|
||||
}
|
||||
var output;
|
||||
if (array) {
|
||||
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
|
||||
} else {
|
||||
output = keys.map(function (key) {
|
||||
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) {
|
||||
var name, str, desc;
|
||||
desc = { value: void 0 };
|
||||
try {
|
||||
// ie6 › navigator.toString
|
||||
// throws Error: Object doesn't support this property or method
|
||||
desc.value = value[key];
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
// ie10 › Object.getOwnPropertyDescriptor(window.location, 'hash')
|
||||
// throws TypeError: Object doesn't support this action
|
||||
if (Object.getOwnPropertyDescriptor) {
|
||||
desc = Object.getOwnPropertyDescriptor(value, key) || desc;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
if (desc.get) {
|
||||
if (desc.set) {
|
||||
str = ctx.stylize('[Getter/Setter]', 'special');
|
||||
} else {
|
||||
str = ctx.stylize('[Getter]', 'special');
|
||||
}
|
||||
} else {
|
||||
if (desc.set) {
|
||||
str = ctx.stylize('[Setter]', 'special');
|
||||
}
|
||||
}
|
||||
if (!hasOwn(visibleKeys, key)) {
|
||||
name = '[' + key + ']';
|
||||
}
|
||||
if (!str) {
|
||||
if (ctx.seen.indexOf(desc.value) < 0) {
|
||||
if (isNull(recurseTimes)) {
|
||||
str = formatValue(ctx, desc.value, null);
|
||||
var name, str, desc;
|
||||
desc = { value: void 0 };
|
||||
try {
|
||||
// ie6 › navigator.toString
|
||||
// throws Error: Object doesn't support this property or method
|
||||
desc.value = value[key];
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
// ie10 › Object.getOwnPropertyDescriptor(window.location, 'hash')
|
||||
// throws TypeError: Object doesn't support this action
|
||||
if (Object.getOwnPropertyDescriptor) {
|
||||
desc = Object.getOwnPropertyDescriptor(value, key) || desc;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
if (desc.get) {
|
||||
if (desc.set) {
|
||||
str = ctx.stylize("[Getter/Setter]", "special");
|
||||
} else {
|
||||
str = ctx.stylize("[Getter]", "special");
|
||||
}
|
||||
} else {
|
||||
if (desc.set) {
|
||||
str = ctx.stylize("[Setter]", "special");
|
||||
}
|
||||
}
|
||||
if (!hasOwn(visibleKeys, key)) {
|
||||
name = "[" + key + "]";
|
||||
}
|
||||
if (!str) {
|
||||
if (ctx.seen.indexOf(desc.value) < 0) {
|
||||
if (isNull(recurseTimes)) {
|
||||
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 {
|
||||
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) {
|
||||
str = str.split('\n').map(function (line) {
|
||||
return ' ' + line;
|
||||
}).join('\n').substr(2);
|
||||
} else {
|
||||
str = '\n' + str.split('\n').map(function (line) {
|
||||
return ' ' + line;
|
||||
}).join('\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
str = ctx.stylize('[Circular]', 'special');
|
||||
}
|
||||
}
|
||||
if (isUndefined(name)) {
|
||||
if (array && key.match(/^\d+$/)) {
|
||||
return str;
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
str = ctx.stylize("[Circular]", "special");
|
||||
}
|
||||
}
|
||||
if (isUndefined(name)) {
|
||||
if (array && key.match(/^\d+$/)) {
|
||||
return str;
|
||||
}
|
||||
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) {
|
||||
if (isUndefined(value))
|
||||
return ctx.stylize('undefined', 'undefined');
|
||||
if (isString(value)) {
|
||||
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
|
||||
if (isUndefined(value)) return ctx.stylize("undefined", "undefined");
|
||||
if (isString(value)) {
|
||||
var simple =
|
||||
"'" +
|
||||
JSON.stringify(value)
|
||||
.replace(/^"|"$/g, "")
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/\\"/g, '"') + '\'';
|
||||
return ctx.stylize(simple, 'string');
|
||||
}
|
||||
if (isNumber(value))
|
||||
return ctx.stylize('' + value, 'number');
|
||||
if (isBoolean(value))
|
||||
return ctx.stylize('' + value, 'boolean');
|
||||
// For some reason typeof null is "object", so special case here.
|
||||
if (isNull(value))
|
||||
return ctx.stylize('null', 'null');
|
||||
.replace(/\\"/g, '"') +
|
||||
"'";
|
||||
return ctx.stylize(simple, "string");
|
||||
}
|
||||
if (isNumber(value)) return ctx.stylize("" + value, "number");
|
||||
if (isBoolean(value)) return ctx.stylize("" + value, "boolean");
|
||||
// For some reason typeof null is "object", so special case here.
|
||||
if (isNull(value)) return ctx.stylize("null", "null");
|
||||
}
|
||||
|
||||
function reduceToSingleString(output, base, braces) {
|
||||
var numLinesEst = 0;
|
||||
var length = output.reduce(function (prev, cur) {
|
||||
numLinesEst++;
|
||||
if (cur.indexOf('\n') >= 0) numLinesEst++;
|
||||
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
|
||||
}, 0);
|
||||
var numLinesEst = 0;
|
||||
var length = output.reduce(function (prev, cur) {
|
||||
numLinesEst++;
|
||||
if (cur.indexOf("\n") >= 0) numLinesEst++;
|
||||
return prev + cur.replace(/\u001b\[\d\d?m/g, "").length + 1;
|
||||
}, 0);
|
||||
|
||||
if (length > 60) {
|
||||
return braces[0] +
|
||||
(base === '' ? '' : base + '\n ') +
|
||||
' ' +
|
||||
output.join(',\n ') +
|
||||
' ' +
|
||||
braces[1];
|
||||
}
|
||||
if (length > 60) {
|
||||
return (
|
||||
braces[0] +
|
||||
(base === "" ? "" : base + "\n ") +
|
||||
" " +
|
||||
output.join(",\n ") +
|
||||
" " +
|
||||
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) {
|
||||
// Don't do anything if add isn't an object
|
||||
if (!add || !isObject(add)) return origin;
|
||||
// Don't do anything if add isn't an object
|
||||
if (!add || !isObject(add)) return origin;
|
||||
|
||||
var keys = Object.keys(add);
|
||||
var i = keys.length;
|
||||
while (i--) {
|
||||
origin[keys[i]] = add[keys[i]];
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
var keys = Object.keys(add);
|
||||
var i = keys.length;
|
||||
while (i--) {
|
||||
origin[keys[i]] = add[keys[i]];
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
41
src/test.ts
41
src/test.ts
@ -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("i", "am", { a: "an" }, 1000);
|
||||
Logging.error(new Error("fehler 001"));
|
||||
Logging.debug("Some Debug infos");
|
||||
Logging.errorMessage("i", "am", "an", "error");
|
||||
Logging.error("i", "am", "an", "error");
|
||||
|
||||
Logging.log(
|
||||
"\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m\x1b[31m TEST \x1b[31m\x1b[31m\x1b[31m"
|
||||
);
|
||||
|
||||
Logging.log(
|
||||
withColor(Colors.MAGENTA, "This text should be magenta!"),
|
||||
"This not!"
|
||||
);
|
||||
Logging.log(withColor(Colors.MAGENTA, { somekey: "Some value" }));
|
||||
Logging.log(Format.magenta("This text should be magenta!"), "This not!");
|
||||
Logging.log(Format.magenta({ somekey: "Some value" }));
|
||||
|
||||
let err = new Error();
|
||||
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("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");
|
||||
|
||||
let cus22 = new LoggingBase("test2");
|
||||
let cus22 = new LoggingBase({ name: "test2" });
|
||||
cus22.log("Hello from custom Logger 22");
|
||||
cus2.log("Hello from custom Logger 2");
|
||||
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.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);
|
||||
|
||||
const withoutFile = new LoggingBase({
|
||||
resolve_filename: false,
|
||||
});
|
||||
|
||||
withoutFile.log("This should not have a file attached");
|
||||
|
337
src/types.ts
337
src/types.ts
@ -1,119 +1,256 @@
|
||||
import { ObservableInterface } from "@hibas123/utils";
|
||||
|
||||
export enum LoggingTypes {
|
||||
Debug,
|
||||
Log,
|
||||
Warning,
|
||||
Error
|
||||
Debug,
|
||||
Log,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
|
||||
export const TerminalFormats = {
|
||||
Reset: "\x1b[0m",
|
||||
Bold: "\x1b[1m",
|
||||
Underscore: "\x1b[4m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
Hidden: "\x1b[8m",
|
||||
Reset: "\x1b[0m",
|
||||
Bold: "\x1b[1m",
|
||||
Underscore: "\x1b[4m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
Hidden: "\x1b[8m",
|
||||
|
||||
FgBlack: "\x1b[30m",
|
||||
FgRed: "\x1b[31m",
|
||||
FgGreen: "\x1b[32m",
|
||||
FgYellow: "\x1b[33m",
|
||||
FgBlue: "\x1b[34m",
|
||||
FgMagenta: "\x1b[35m",
|
||||
FgCyan: "\x1b[36m",
|
||||
FgWhite: "\x1b[37m",
|
||||
FgBlack: "\x1b[30m",
|
||||
FgRed: "\x1b[31m",
|
||||
FgGreen: "\x1b[32m",
|
||||
FgYellow: "\x1b[33m",
|
||||
FgBlue: "\x1b[34m",
|
||||
FgMagenta: "\x1b[35m",
|
||||
FgCyan: "\x1b[36m",
|
||||
FgWhite: "\x1b[37m",
|
||||
|
||||
BgBlack: "\x1b[40m",
|
||||
BgRed: "\x1b[41m",
|
||||
BgGreen: "\x1b[42m",
|
||||
BgYellow: "\x1b[43m",
|
||||
BgBlue: "\x1b[44m",
|
||||
BgMagenta: "\x1b[45m",
|
||||
BgCyan: "\x1b[46m",
|
||||
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
|
||||
BgBlack: "\x1b[40m",
|
||||
BgRed: "\x1b[41m",
|
||||
BgGreen: "\x1b[42m",
|
||||
BgYellow: "\x1b[43m",
|
||||
BgBlue: "\x1b[44m",
|
||||
BgMagenta: "\x1b[45m",
|
||||
BgCyan: "\x1b[46m",
|
||||
BgWhite: "\x1b[47m",
|
||||
};
|
||||
|
||||
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 {
|
||||
error = [colorFormat(Colors.RED), boldFormat];
|
||||
warning = [colorFormat(Colors.YELLOW), boldFormat];
|
||||
log = [colorFormat(Colors.NONE), boldFormat];
|
||||
debug = [colorFormat(Colors.CYAN), boldFormat];
|
||||
error = new Formatted()._color(IColors.RED)._bold();
|
||||
warning = new Formatted()._color(IColors.YELLOW)._bold();
|
||||
log = new Formatted()._color(IColors.NONE)._bold();
|
||||
debug = new Formatted()._color(IColors.CYAN)._bold();
|
||||
|
||||
date = [colorFormat(Colors.NONE)];
|
||||
file = [colorFormat(Colors.NONE)];
|
||||
date = new Formatted()._color(IColors.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 {
|
||||
type: LoggingTypes;
|
||||
name?: string;
|
||||
text: {
|
||||
raw: string[],
|
||||
formatted: FormattedLine[]
|
||||
};
|
||||
date: Date;
|
||||
file: string;
|
||||
type: LoggingTypes;
|
||||
names?: string[];
|
||||
text: IFormatted<string>[];
|
||||
date: Date;
|
||||
file: string;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "esm",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": true,
|
||||
"outDir": "out",
|
||||
"declaration": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user