Compare commits
5 Commits
8073e044a7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a935f220fc | |||
| 35ccdb5418 | |||
| 1a5e1d5a74 | |||
| 8a8b68ed40 | |||
| df31d60af9 |
109
README.md
109
README.md
@@ -1,5 +1,5 @@
|
|||||||
# Undead Logging
|
# Undead Logging
|
||||||
Logging for stupid idiots like me
|
Opinionated logging
|
||||||
|
|
||||||
* Pluggable event listeners
|
* Pluggable event listeners
|
||||||
* Per-source hushing, global log type hushing
|
* Per-source hushing, global log type hushing
|
||||||
@@ -39,10 +39,10 @@ log.n("Something happened");
|
|||||||
|
|
||||||
Disable a type of message globally:
|
Disable a type of message globally:
|
||||||
```ts
|
```ts
|
||||||
import Logging, { MessageTypeVisibility } from "@proxnet/undead-logging";
|
import Logging, { LoggingConfiguration } from "@proxnet/undead-logging";
|
||||||
// .. logging source "Main" is in the scope
|
// .. logging source "Main" is in the scope
|
||||||
|
|
||||||
MessageTypeVisibility.Error = false;
|
LoggingConfiguration.MessageTypeVisibility.Error = false;
|
||||||
|
|
||||||
// somewhere else, could be a different script
|
// somewhere else, could be a different script
|
||||||
log.i("I am visible");
|
log.i("I am visible");
|
||||||
@@ -87,6 +87,8 @@ LoggingListeners.offmsg('basic', cb);
|
|||||||
## Time display modes
|
## Time display modes
|
||||||
|
|
||||||
You can display four different formats for time:
|
You can display four different formats for time:
|
||||||
|
* No time display
|
||||||
|
- Can be useful when running a service that already logs time and date, such as systemd services
|
||||||
* UTC
|
* UTC
|
||||||
* Unix time (in milliseconds)
|
* Unix time (in milliseconds)
|
||||||
* Local [process] time (in milliseconds, from the `performance` API)
|
* Local [process] time (in milliseconds, from the `performance` API)
|
||||||
@@ -99,6 +101,8 @@ import { LoggingConfiguration, TimeFormat } from "@proxnet/undead-logging";
|
|||||||
LoggingConfiguration.timeFormat = TimeFormat.Local;
|
LoggingConfiguration.timeFormat = TimeFormat.Local;
|
||||||
// or
|
// or
|
||||||
LoggingConfiguration.timeFormat = TimeFormat.Utc;
|
LoggingConfiguration.timeFormat = TimeFormat.Utc;
|
||||||
|
// or even
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.None; // removes the time and date section of the log line entirely
|
||||||
```
|
```
|
||||||
|
|
||||||
## (advanced) Logging timing
|
## (advanced) Logging timing
|
||||||
@@ -118,10 +122,10 @@ You might also prefer to reset all sources to synchronous logging during app shu
|
|||||||
This can help debug issues with how your app closes. (see example below)
|
This can help debug issues with how your app closes. (see example below)
|
||||||
|
|
||||||
## Global resets
|
## Global resets
|
||||||
Every source is tracked by this module in a `Set`.
|
Every source is tracked by `LoggingConfiguration` in a `Set`.
|
||||||
|
|
||||||
You can reset every source's log timing or time format with `LoggingConfiguration.reset(something)`.<br>
|
You can reset every source's log timing or time format with `LoggingConfiguration.resetX`.<br>
|
||||||
The corresponding property will be updated on `LoggingConfiguration`:
|
The corresponding property will be updated on all instances of `Logging`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import Logging, { LoggingConfiguration, LogTiming } from "@proxnet/undead-logging";
|
import Logging, { LoggingConfiguration, LogTiming } from "@proxnet/undead-logging";
|
||||||
@@ -149,7 +153,98 @@ Deno.addSignalListener('SIGINT', () => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## [Benchmarks](./BENCH.md)
|
## Class/Object Conversion
|
||||||
|
Any type of object can be given to a logging function. "[object Object]" may not always be desirable.
|
||||||
|
|
||||||
|
You can automatically convert any object to a string of any format using a `Conversion<T>`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class CustomClass {
|
||||||
|
|
||||||
|
foo: string;
|
||||||
|
bar: number;
|
||||||
|
|
||||||
|
constructor(foo: string, bar: number) {
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggingConfiguration.addConversion<CustomClass>({
|
||||||
|
condition: arg => arg instanceof CustomClass,
|
||||||
|
converter: arg => `CustomClass; foo:${arg.foo}; bar:${arg.bar};`
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
log an instance of `CustomClass`:
|
||||||
|
2026-01-04T23:56:49.156Z ConvertTest [DEBUG] CustomClass; foo:baz; bar:39;
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
Promises are honored when using them in the `condition` and/or the `converter`.
|
||||||
|
<br>As such, you can use your own async in the conversion should you ever want to.
|
||||||
|
|
||||||
|
Some classes and objects are converted by default, such as `Response` and `Request` standard APIs.
|
||||||
|
<br>They are demonstrated in `tests/convert.ts`, though they appear as:
|
||||||
|
```
|
||||||
|
2026-01-04T23:56:48.965Z Web [INFO] GET http://example.com/?hello=world
|
||||||
|
key1: value1
|
||||||
|
key2: value2
|
||||||
|
2026-01-04T23:56:49.156Z Web [INFO] 200 OK https://example.com/
|
||||||
|
age: 8217
|
||||||
|
...
|
||||||
|
server: cloudflare
|
||||||
|
vary: Accept-Encoding
|
||||||
|
```
|
||||||
|
|
||||||
|
You can remove these default conversions using `LoggingConfiguration.clearConversions()` before your program starts.
|
||||||
|
|
||||||
|
### Converter priority
|
||||||
|
In semi-rare cases, you may want to prefer using some converters over others,<br>such as converting custom `Error`s rather than the base `Error` class.
|
||||||
|
|
||||||
|
You can configure converter *priority* using `Conversion.priority`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class CustomError extends Error {
|
||||||
|
|
||||||
|
someProperty: string
|
||||||
|
|
||||||
|
constructor(someProp: string) {
|
||||||
|
super("Something went wrong.");
|
||||||
|
this.someProperty = someProp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggingConfiguration.clearConversions();
|
||||||
|
LoggingConfiguration.addConversion<CustomError>({
|
||||||
|
condition: arg => arg instanceof CustomError,
|
||||||
|
converter: arg => `CustomError: someProperty:${arg.someProperty}; ${arg.stack || arg.message}`,
|
||||||
|
priority: -1
|
||||||
|
});
|
||||||
|
LoggingConfiguration.addConversion<Error>({
|
||||||
|
condition: arg => arg instanceof Error,
|
||||||
|
converter: arg => `${arg.stack || arg.message}`,
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
CustomError at 1, Error at -1:
|
||||||
|
|
||||||
|
2026-01-05T00:09:18.176Z PriorityTest [INFO] CustomError: someProperty:'Hello World!'; Error: Something went wrong.
|
||||||
|
at file:///C:/Users/zombieb/undead-logging/tests/priority.ts:28:7
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
CustomError at -1, Error at 1:
|
||||||
|
|
||||||
|
2026-01-05T00:09:34.040Z PriorityTest [INFO] Error: Something went wrong.
|
||||||
|
at file:///C:/Users/zombieb/undead-logging/tests/priority.ts:28:7
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
Converters with numbers closer to `Infinity` have a higher priority. As such, you can choose your numerical layout for converter priority.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
create an account on gitea.proxnet.dev, fork, then PR
|
create an account on gitea.proxnet.dev, fork, then PR
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import chalk from "chalk";
|
|
||||||
import Logging, { MessageType } from "../mod.ts";
|
|
||||||
|
|
||||||
interface BenchStats {
|
|
||||||
avg: number,
|
|
||||||
med: number,
|
|
||||||
rng: number,
|
|
||||||
min: number,
|
|
||||||
max: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBench(n: number) {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
const data: number[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 30; i++) {
|
|
||||||
const last = performance.now();
|
|
||||||
for (let i = 0; i < n; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
data.push(performance.now() - last);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sorted = [...data].sort((a, b) => a - b);
|
|
||||||
return {
|
|
||||||
n,
|
|
||||||
avg: data.reduce((sum, val) => sum + val, 0) / data.length,
|
|
||||||
med: sorted.length % 2 === 0
|
|
||||||
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
|
|
||||||
: sorted[Math.floor(sorted.length / 2)],
|
|
||||||
rng: Math.max(...data) - Math.min(...data),
|
|
||||||
min: Math.min(...data),
|
|
||||||
max: Math.max(...data)
|
|
||||||
} as BenchStats;
|
|
||||||
}
|
|
||||||
|
|
||||||
const benches = [
|
|
||||||
createBench(1),
|
|
||||||
createBench(100),
|
|
||||||
createBench(1000),
|
|
||||||
createBench(10000),
|
|
||||||
createBench(100000),
|
|
||||||
];
|
|
||||||
|
|
||||||
function trimNumber(n: number) {
|
|
||||||
const split = n.toString().split('.');
|
|
||||||
if (!split[1]) return n;
|
|
||||||
else if (split[1].length > 4) return `${split[0]}.${split[1].substring(0, 4)}`;
|
|
||||||
else if (split[1].length == 0) return n.toString();
|
|
||||||
else return split.join('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const stats of benches) {
|
|
||||||
for (const value of Object.values(stats)) {
|
|
||||||
|
|
||||||
const label = value < 1 ? 'µs' : 'ms';
|
|
||||||
const val = value < 1 ? value * 1000 : value;
|
|
||||||
|
|
||||||
const i = Object.values(stats).indexOf(value);
|
|
||||||
const key = Object.keys(stats).find((_val, index) => index == i);
|
|
||||||
|
|
||||||
console.log(`${key}: ${trimNumber(val)}${key !== 'n' ? label : ''}`);
|
|
||||||
}
|
|
||||||
console.log('');
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"runtime": "Deno/2.4.1 x86_64-pc-windows-msvc",
|
|
||||||
"cpu": "unknown",
|
|
||||||
"benches": [
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-sync",
|
|
||||||
"name": "1 Log",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 42,
|
|
||||||
"min": 1547.66,
|
|
||||||
"max": 1867.93,
|
|
||||||
"avg": 1596.3097619047621,
|
|
||||||
"p75": 1602.51,
|
|
||||||
"p99": 1867.93,
|
|
||||||
"p995": 1867.93,
|
|
||||||
"p999": 1867.93,
|
|
||||||
"highPrecision": false,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-sync",
|
|
||||||
"name": "100 Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 3227,
|
|
||||||
"min": 149200.0,
|
|
||||||
"max": 277100.0,
|
|
||||||
"avg": 155426.0,
|
|
||||||
"p75": 151700.0,
|
|
||||||
"p99": 230500.0,
|
|
||||||
"p995": 233300.0,
|
|
||||||
"p999": 250500.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-sync",
|
|
||||||
"name": "1k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 332,
|
|
||||||
"min": 1492200.0,
|
|
||||||
"max": 2041200.0,
|
|
||||||
"avg": 1553763.0,
|
|
||||||
"p75": 1581400.0,
|
|
||||||
"p99": 1678900.0,
|
|
||||||
"p995": 1847700.0,
|
|
||||||
"p999": 2041200.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-sync",
|
|
||||||
"name": "10k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 43,
|
|
||||||
"min": 15433100.0,
|
|
||||||
"max": 15875500.0,
|
|
||||||
"avg": 15512717.0,
|
|
||||||
"p75": 15516000.0,
|
|
||||||
"p99": 15875500.0,
|
|
||||||
"p995": 15875500.0,
|
|
||||||
"p999": 15875500.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-sync",
|
|
||||||
"name": "100k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 14,
|
|
||||||
"min": 154448800.0,
|
|
||||||
"max": 162192300.0,
|
|
||||||
"avg": 156260422.0,
|
|
||||||
"p75": 156585200.0,
|
|
||||||
"p99": 162192300.0,
|
|
||||||
"p995": 162192300.0,
|
|
||||||
"p999": 162192300.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-deferred",
|
|
||||||
"name": "1 Log",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 42,
|
|
||||||
"min": 1553.59,
|
|
||||||
"max": 1623.61,
|
|
||||||
"avg": 1581.7592857142856,
|
|
||||||
"p75": 1594.76,
|
|
||||||
"p99": 1623.61,
|
|
||||||
"p995": 1623.61,
|
|
||||||
"p999": 1623.61,
|
|
||||||
"highPrecision": false,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-deferred",
|
|
||||||
"name": "100 Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 3205,
|
|
||||||
"min": 148800.0,
|
|
||||||
"max": 357900.0,
|
|
||||||
"avg": 156507.0,
|
|
||||||
"p75": 152200.0,
|
|
||||||
"p99": 233300.0,
|
|
||||||
"p995": 241400.0,
|
|
||||||
"p999": 273500.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-deferred",
|
|
||||||
"name": "1k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 330,
|
|
||||||
"min": 1504500.0,
|
|
||||||
"max": 1744900.0,
|
|
||||||
"avg": 1564083.0,
|
|
||||||
"p75": 1595100.0,
|
|
||||||
"p99": 1668200.0,
|
|
||||||
"p995": 1732400.0,
|
|
||||||
"p999": 1744900.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-deferred",
|
|
||||||
"name": "10k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 43,
|
|
||||||
"min": 15435800.0,
|
|
||||||
"max": 15980800.0,
|
|
||||||
"avg": 15641247.0,
|
|
||||||
"p75": 15771900.0,
|
|
||||||
"p99": 15980800.0,
|
|
||||||
"p995": 15980800.0,
|
|
||||||
"p999": 15980800.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "file:///path/to/undead-logging/benchmark/deno.ts",
|
|
||||||
"group": "timing-deferred",
|
|
||||||
"name": "100k Logs",
|
|
||||||
"baseline": false,
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"ok": {
|
|
||||||
"n": 14,
|
|
||||||
"min": 154775900.0,
|
|
||||||
"max": 159622500.0,
|
|
||||||
"avg": 156075222.0,
|
|
||||||
"p75": 156708400.0,
|
|
||||||
"p99": 159622500.0,
|
|
||||||
"p995": 159622500.0,
|
|
||||||
"p999": 159622500.0,
|
|
||||||
"highPrecision": true,
|
|
||||||
"usedExplicitTimers": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import Logging, { LogTiming, MessageType } from "@proxnet/undead-logging";
|
|
||||||
import chalk from "chalk";
|
|
||||||
|
|
||||||
// SYNC
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "1 Log",
|
|
||||||
group: "timing-sync",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "100 Logs",
|
|
||||||
group: "timing-sync",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
for (let i = 0; i < 100; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "1k Logs",
|
|
||||||
group: "timing-sync",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
for (let i = 0; i < 1_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "10k Logs",
|
|
||||||
group: "timing-sync",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
for (let i = 0; i < 10_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "100k Logs",
|
|
||||||
group: "timing-sync",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.logTiming = LogTiming.Deferred;
|
|
||||||
for (let i = 0; i < 100_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// DEFERRED
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "1 Log",
|
|
||||||
group: "timing-deferred",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.logTiming = LogTiming.Deferred;
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "100 Logs",
|
|
||||||
group: "timing-deferred",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.logTiming = LogTiming.Deferred;
|
|
||||||
for (let i = 0; i < 100; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "1k Logs",
|
|
||||||
group: "timing-deferred",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.logTiming = LogTiming.Deferred;
|
|
||||||
for (let i = 0; i < 1_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "10k Logs",
|
|
||||||
group: "timing-deferred",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
log.logTiming = LogTiming.Deferred;
|
|
||||||
for (let i = 0; i < 10_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.bench({
|
|
||||||
name: "100k Logs",
|
|
||||||
group: "timing-deferred",
|
|
||||||
fn: () => {
|
|
||||||
const log = new Logging("Bench");
|
|
||||||
for (let i = 0; i < 100_000; i++)
|
|
||||||
log.bench(MessageType.Info, chalk.black, 'a');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
15
deno.json
15
deno.json
@@ -7,14 +7,13 @@
|
|||||||
"bench": "deno bench -A"
|
"bench": "deno bench -A"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@1",
|
"@neabyte/deno-ansi": "jsr:@neabyte/deno-ansi@^0.1.0"
|
||||||
"chalk": "npm:chalk@^5.3.0"
|
|
||||||
},
|
},
|
||||||
"exports": "./mod.ts",
|
"exports": {
|
||||||
"version": "1.5.1",
|
".": "./src/mod.ts",
|
||||||
|
"./types": "./src/types.ts"
|
||||||
|
},
|
||||||
|
"version": "1.6.0",
|
||||||
"name": "@proxnet/undead-logging",
|
"name": "@proxnet/undead-logging",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"bench": {
|
|
||||||
"include": ["./benchmark/deno.ts"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
22
deno.lock
generated
22
deno.lock
generated
@@ -1,20 +1,12 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@std/assert@1": "1.0.13",
|
"jsr:@neabyte/deno-ansi@0.1": "0.1.0",
|
||||||
"jsr:@std/internal@^1.0.6": "1.0.9",
|
"npm:@types/node@*": "22.15.15"
|
||||||
"npm:@types/node@*": "22.15.15",
|
|
||||||
"npm:chalk@^5.3.0": "5.4.1"
|
|
||||||
},
|
},
|
||||||
"jsr": {
|
"jsr": {
|
||||||
"@std/assert@1.0.13": {
|
"@neabyte/deno-ansi@0.1.0": {
|
||||||
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
|
"integrity": "3affddb394ce77feb2c40b3d9f8ae3c2045f8bc721038c2d6610f09db37edf72"
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/internal"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/internal@1.0.9": {
|
|
||||||
"integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
@@ -24,17 +16,13 @@
|
|||||||
"undici-types"
|
"undici-types"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"chalk@5.4.1": {
|
|
||||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="
|
|
||||||
},
|
|
||||||
"undici-types@6.21.0": {
|
"undici-types@6.21.0": {
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/assert@1",
|
"jsr:@neabyte/deno-ansi@0.1"
|
||||||
"npm:chalk@^5.3.0"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
373
mod.ts
373
mod.ts
@@ -1,373 +0,0 @@
|
|||||||
import chalk from "chalk";
|
|
||||||
import { setImmediate } from "node:timers";
|
|
||||||
import process from "node:process";
|
|
||||||
|
|
||||||
type Message = unknown;
|
|
||||||
type ChalkFunction = (...msgs: Message[]) => string;
|
|
||||||
type PrimitiveItems = Message[];
|
|
||||||
|
|
||||||
interface UnknownConversion<T> {
|
|
||||||
condition: (arg: Message) => boolean;
|
|
||||||
converter: (arg: T) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** How would you like your logs served? */
|
|
||||||
interface LoggingOptions {
|
|
||||||
logTiming?: LogTiming,
|
|
||||||
timeFormat?: TimeFormat,
|
|
||||||
bright?: boolean,
|
|
||||||
silent?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Control all log sources from this module */
|
|
||||||
const sources: Set<Logging> = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A source for pretty and cool and fun logging
|
|
||||||
*/
|
|
||||||
class Logging {
|
|
||||||
|
|
||||||
/** Can I be seen by the console and listeners? */
|
|
||||||
visible: boolean
|
|
||||||
/** What is my name? */
|
|
||||||
source: string
|
|
||||||
/** Should I use bright colors over dull ones? */
|
|
||||||
bright: boolean = true;
|
|
||||||
|
|
||||||
/** Control when logs are handled for this source - similar (defaults to) `LoggingConfiguration.logTiming` */
|
|
||||||
logTiming: LogTiming = LoggingConfiguration.logTiming;
|
|
||||||
/** Control how time is displayed for this source - similar (defaults to) `LoggingConfiguration.timeFormat` */
|
|
||||||
timeFormat: TimeFormat = LoggingConfiguration.timeFormat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a logging source
|
|
||||||
* ```ts
|
|
||||||
* const log = new Logging("Main");
|
|
||||||
*
|
|
||||||
* log.i("Hello World!");
|
|
||||||
* ```
|
|
||||||
* @param source Module identifier. Used in every line to identify the module that sent the message.
|
|
||||||
* @param silent Set to false to log a message when the logger instantiates. May be useful when debugging.
|
|
||||||
* @returns A source for logging messages to the console (stdout). Functions for info, warnings, errors, debug statements, and network events are provided and have shorthands.
|
|
||||||
*/
|
|
||||||
constructor(source: string, options?: LoggingOptions) {
|
|
||||||
this.visible = true;
|
|
||||||
this.source = source;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.bright) this.bright = options.bright;
|
|
||||||
if (typeof options.silent == 'boolean' && !options.silent) this.info(`Instantiated logging for ${this.source}`);
|
|
||||||
if (options.logTiming) this.logTiming = options.logTiming;
|
|
||||||
if (options.timeFormat) this.timeFormat = options.timeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
sources.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#conversions(...msgs: PrimitiveItems) {
|
|
||||||
const conversions = [
|
|
||||||
{
|
|
||||||
condition: arg => arg instanceof Error,
|
|
||||||
converter: arg => arg.stack || arg.message
|
|
||||||
} as UnknownConversion<Error>,
|
|
||||||
{
|
|
||||||
condition: arg => arg == null,
|
|
||||||
converter: arg => JSON.stringify(arg)
|
|
||||||
} as UnknownConversion<null>,
|
|
||||||
{
|
|
||||||
condition: arg => arg == undefined,
|
|
||||||
converter: _arg => 'undefined'
|
|
||||||
} as UnknownConversion<undefined>,
|
|
||||||
{
|
|
||||||
condition: arg => arg instanceof Response,
|
|
||||||
converter: arg => {
|
|
||||||
try {
|
|
||||||
const url = arg.url.toString().length == 0 ? '(unknown origin) ' : new URL(arg.url);
|
|
||||||
const statusText = arg.statusText.length > 0 ? `${arg.statusText} ` : '';
|
|
||||||
|
|
||||||
const shouldNewline = Array.from(arg.headers.keys()).length > 0;
|
|
||||||
const entries = Array.from(arg.headers.entries());
|
|
||||||
return (
|
|
||||||
`${arg.status} ${statusText}${url}${shouldNewline ? '\n ' : ''}` +
|
|
||||||
entries.map(val => `${val[0]}: ${val[1]}`).join(`\n `)
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return String(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as UnknownConversion<Response>,
|
|
||||||
{
|
|
||||||
condition: arg => arg instanceof Request,
|
|
||||||
converter: arg => {
|
|
||||||
try {
|
|
||||||
const url = new URL(arg.url);
|
|
||||||
const shouldNewline = Array.from(arg.headers.keys()).length > 0;
|
|
||||||
const entries = Array.from(arg.headers.entries());
|
|
||||||
return `${arg.method} ${url}${shouldNewline ? '\n ' : ''}${entries.map(val => `${val[0]}: ${val[1]}`).join(`\n `)}`;
|
|
||||||
} catch {
|
|
||||||
return String(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as UnknownConversion<Request>,
|
|
||||||
{
|
|
||||||
condition: arg => typeof arg == 'object',
|
|
||||||
converter: arg => JSON.stringify(arg)
|
|
||||||
} as UnknownConversion<object>,
|
|
||||||
];
|
|
||||||
function convertToString(arg: Message) {
|
|
||||||
for (const conversion of conversions)
|
|
||||||
if (conversion.condition(arg))
|
|
||||||
return (conversion.converter as (arg: unknown) => string)(arg);
|
|
||||||
// gpt-4o idea cuz my brain has the `stupid` type
|
|
||||||
|
|
||||||
return String(arg); // fallback
|
|
||||||
}
|
|
||||||
return msgs.map(val => convertToString(val)).join(' ');
|
|
||||||
}
|
|
||||||
#typeStr(type: MessageType) {
|
|
||||||
switch (type) {
|
|
||||||
case MessageType.Info:
|
|
||||||
return '[INFO]';
|
|
||||||
case MessageType.Warn:
|
|
||||||
return '[WARN]';
|
|
||||||
case MessageType.Error:
|
|
||||||
return '[ERROR]';
|
|
||||||
case MessageType.Debug:
|
|
||||||
return '[DEBUG]';
|
|
||||||
case MessageType.Network:
|
|
||||||
return '[NETWORK]';
|
|
||||||
default:
|
|
||||||
return '[UNKNOWN]';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#timeStr(time: Date) {
|
|
||||||
switch (this.timeFormat) {
|
|
||||||
case TimeFormat.Unix:
|
|
||||||
return time.getTime();
|
|
||||||
case TimeFormat.Local:
|
|
||||||
return performance.now();
|
|
||||||
case TimeFormat.RoundedLocal:
|
|
||||||
return Math.round(performance.now());
|
|
||||||
default:
|
|
||||||
return time.toISOString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't use this unless you're benchmarking.
|
|
||||||
*
|
|
||||||
* Constructs a message. Does not do anything with it, or even return it.
|
|
||||||
*
|
|
||||||
* Logging messages during [Deno] benchmarks causes problems.
|
|
||||||
*/
|
|
||||||
bench(type: MessageType, chalkColor: ChalkFunction, ...msgs: PrimitiveItems) {
|
|
||||||
`${chalk.gray(`${this.#timeStr(new Date())} `)} ${chalkColor(chalk.inverse(`${this.source} ${this.#typeStr(type)}`) + ` ${this.#conversions(...msgs)}`)}`;
|
|
||||||
}
|
|
||||||
#log(type: MessageType, chalkColor: ChalkFunction, ...msgs: PrimitiveItems) {
|
|
||||||
if (!this.visible) return;
|
|
||||||
const func = () => {
|
|
||||||
|
|
||||||
const time = new Date();
|
|
||||||
|
|
||||||
const str = this.#conversions(...msgs);
|
|
||||||
const msg = `${chalk.gray(this.#timeStr(time))} ${chalkColor(chalk.inverse(`${this.source} ${this.#typeStr(type)}`) + ` ${str}`)}`
|
|
||||||
|
|
||||||
process.stdout.write(`${msg}\r\n`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoggingListeners.emit('basic', msg);
|
|
||||||
LoggingListeners.emit('type', str, type, this.source, time);
|
|
||||||
} catch (err) {
|
|
||||||
process.stderr.write(`(FALLBACK ERROR) Callback failed! Stack: ${(err as Error).stack}\r\n`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.logTiming) {
|
|
||||||
case LogTiming.Sync:
|
|
||||||
func();
|
|
||||||
break;
|
|
||||||
case LogTiming.Deferred:
|
|
||||||
setImmediate(func);
|
|
||||||
break;
|
|
||||||
case LogTiming.Microtask:
|
|
||||||
if (typeof queueMicrotask == 'function')
|
|
||||||
queueMicrotask(func);
|
|
||||||
else
|
|
||||||
setTimeout(func, 0); // fallback
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
func();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param type The kind of message to log
|
|
||||||
* @param msgs Your message(s)
|
|
||||||
*/
|
|
||||||
log(type: MessageType, ...msgs: PrimitiveItems) {
|
|
||||||
switch (type) {
|
|
||||||
case MessageType.Info:
|
|
||||||
this.info(...msgs);
|
|
||||||
break;
|
|
||||||
case MessageType.Warn:
|
|
||||||
this.warn(...msgs);
|
|
||||||
break;
|
|
||||||
case MessageType.Error:
|
|
||||||
this.error(...msgs);
|
|
||||||
break;
|
|
||||||
case MessageType.Debug:
|
|
||||||
this.debug(...msgs);
|
|
||||||
break;
|
|
||||||
case MessageType.Network:
|
|
||||||
this.network(...msgs);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.info(...msgs);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logging Function */
|
|
||||||
i(...msgs: PrimitiveItems) { this.info(...msgs); }
|
|
||||||
/** Logging Function */
|
|
||||||
info(...msgs: PrimitiveItems) {
|
|
||||||
if (!MessageTypeVisibility.Info) return;
|
|
||||||
this.#log(MessageType.Info, chalk.whiteBright, ...msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logging Function */
|
|
||||||
w(...msgs: PrimitiveItems) { this.warn(...msgs); }
|
|
||||||
/** Logging Function */
|
|
||||||
warn(...msgs: PrimitiveItems) {
|
|
||||||
if (!MessageTypeVisibility.Warn) return;
|
|
||||||
this.#log(MessageType.Warn, chalk.yellowBright, ...msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logging Function */
|
|
||||||
e(...msgs: PrimitiveItems) { this.error(...msgs); }
|
|
||||||
/** Logging Function */
|
|
||||||
error(...msgs: PrimitiveItems) {
|
|
||||||
if (!MessageTypeVisibility.Error) return;
|
|
||||||
this.#log(MessageType.Error, chalk.redBright, ...msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logging Function */
|
|
||||||
d(...msgs: PrimitiveItems) { this.debug(...msgs); }
|
|
||||||
/** Logging Function */
|
|
||||||
debug(...msgs: PrimitiveItems) {
|
|
||||||
if (!MessageTypeVisibility.Debug) return;
|
|
||||||
this.#log(MessageType.Debug, chalk.greenBright, ...msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logging Function */
|
|
||||||
n(...msgs: PrimitiveItems) { this.network(...msgs); }
|
|
||||||
/** Logging Function */
|
|
||||||
network(...msgs: PrimitiveItems) {
|
|
||||||
if (!MessageTypeVisibility.Network) return;
|
|
||||||
this.#log(MessageType.Network, chalk.cyanBright, ...msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Useful for conditional logging with the `log` function */
|
|
||||||
enum MessageType {
|
|
||||||
Info,
|
|
||||||
Warn,
|
|
||||||
Error,
|
|
||||||
Debug,
|
|
||||||
Network
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Enable/disable logging of certain message types across all logging sources */
|
|
||||||
const MessageTypeVisibility = {
|
|
||||||
Info: true,
|
|
||||||
Warn: true,
|
|
||||||
Error: true,
|
|
||||||
Debug: true,
|
|
||||||
Network: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'On Message' event listeners
|
|
||||||
type BasicListener = (msg: string) => unknown;
|
|
||||||
type TypeListener = (msg: string, type: MessageType, source: string, time: Date) => unknown;
|
|
||||||
type Listener = BasicListener | TypeListener;
|
|
||||||
type ListenerType = 'basic' | 'type';
|
|
||||||
|
|
||||||
const basicListeners = new Set<Listener>();
|
|
||||||
const typeListeners = new Set<Listener>();
|
|
||||||
|
|
||||||
class ListenersBase {
|
|
||||||
/** Register listener callback */
|
|
||||||
onmsg(type: 'basic', cb: BasicListener): void
|
|
||||||
onmsg(type: 'type', cb: TypeListener): void
|
|
||||||
onmsg(type: ListenerType, cb: Listener) {
|
|
||||||
if (type == 'basic') basicListeners.add(cb);
|
|
||||||
else if (type == 'type') typeListeners.add(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove listener callback */
|
|
||||||
offmsg(type: 'basic', cb: BasicListener): void
|
|
||||||
offmsg(type: 'type', cb: TypeListener): void
|
|
||||||
offmsg(type: ListenerType, cb: Listener) {
|
|
||||||
if (type == 'basic') basicListeners.delete(cb);
|
|
||||||
else if (type == 'type') typeListeners.delete(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Emit a log line. Usually not needed outside this module. */
|
|
||||||
emit(type: 'basic', msg: string, msgtype?: MessageType, source?: string, time?: Date): void
|
|
||||||
emit(type: 'type', msg: string, msgtype: MessageType, source: string, time: Date): void
|
|
||||||
emit(type: ListenerType, msg: string, msgtype: MessageType, source: string, time: Date) {
|
|
||||||
if (type == 'basic')
|
|
||||||
for (const cb of basicListeners) (cb as BasicListener)(msg);
|
|
||||||
if (type == 'type')
|
|
||||||
for (const cb of typeListeners) (cb as TypeListener)(msg, msgtype, source, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add/remove callbacks: run when something is logged anywhere
|
|
||||||
*/
|
|
||||||
const LoggingListeners: ListenersBase = new ListenersBase();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify when, in/around the event loop, logs are sent
|
|
||||||
*/
|
|
||||||
enum LogTiming {
|
|
||||||
Sync,
|
|
||||||
Deferred,
|
|
||||||
Microtask
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Specify the format that loggers use to display time
|
|
||||||
*/
|
|
||||||
enum TimeFormat {
|
|
||||||
Utc,
|
|
||||||
Unix,
|
|
||||||
Local,
|
|
||||||
RoundedLocal
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoggingConfigurationBase {
|
|
||||||
|
|
||||||
#logTiming: LogTiming = LogTiming.Sync;
|
|
||||||
|
|
||||||
#timeFormat: TimeFormat = TimeFormat.Utc;
|
|
||||||
|
|
||||||
get timeFormat(): TimeFormat { return this.#timeFormat }
|
|
||||||
set timeFormat(data: TimeFormat) { this.#timeFormat = data }
|
|
||||||
|
|
||||||
get logTiming(): LogTiming { return this.#logTiming; }
|
|
||||||
set logTiming(data: LogTiming) { this.#logTiming = data }
|
|
||||||
|
|
||||||
set resetTimeFormat(data: TimeFormat) { for (const source of sources.values()) source.timeFormat = data; this.#timeFormat = data; }
|
|
||||||
set resetLogTiming(data: LogTiming) { for (const source of sources.values()) source.logTiming = data; this.#logTiming = data; }
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Configure time display format and log timing here
|
|
||||||
*/
|
|
||||||
const LoggingConfiguration: LoggingConfigurationBase = new LoggingConfigurationBase();
|
|
||||||
|
|
||||||
export { MessageType, TimeFormat, LogTiming, MessageTypeVisibility, LoggingListeners, LoggingConfiguration };
|
|
||||||
export default Logging;
|
|
||||||
379
src/mod.ts
Normal file
379
src/mod.ts
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import { setImmediate } from "node:timers";
|
||||||
|
import process from "node:process";
|
||||||
|
import type { Conversion, LoggingOptions, Message } from "./types.ts";
|
||||||
|
import { TimeFormat, MessageType, LogTiming } from "./types.ts";
|
||||||
|
import * as ANSI from "@neabyte/deno-ansi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A source for pretty and cool and fun logging
|
||||||
|
*/
|
||||||
|
class Logging {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line ending for this platform.
|
||||||
|
*/
|
||||||
|
static getNewline(): '\r\n' | '\n' {
|
||||||
|
return Deno.build.os === 'windows' ? '\r\n' : '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
static timeStr(format: TimeFormat, time: Date): string {
|
||||||
|
switch (format) {
|
||||||
|
case TimeFormat.None:
|
||||||
|
return '';
|
||||||
|
case TimeFormat.Unix:
|
||||||
|
return `${time.getTime()} `;
|
||||||
|
case TimeFormat.Local:
|
||||||
|
return `${performance.now()} `;
|
||||||
|
case TimeFormat.RoundedLocal:
|
||||||
|
return `${Math.round(performance.now())} `;
|
||||||
|
case TimeFormat.Utc:
|
||||||
|
return `${time.toISOString()} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static typeStr(type: MessageType): string {
|
||||||
|
switch (type) {
|
||||||
|
case MessageType.Info:
|
||||||
|
return '[INFO]';
|
||||||
|
case MessageType.Warn:
|
||||||
|
return '[WARN]';
|
||||||
|
case MessageType.Error:
|
||||||
|
return '[ERROR]';
|
||||||
|
case MessageType.Debug:
|
||||||
|
return '[DEBUG]';
|
||||||
|
case MessageType.Network:
|
||||||
|
return '[NETWORK]';
|
||||||
|
default:
|
||||||
|
return '[UNKNOWN]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Can I be seen by the console and listeners? */
|
||||||
|
visible: boolean
|
||||||
|
/** What is my name? */
|
||||||
|
source: string
|
||||||
|
/** Should I use bright colors over dull ones? */
|
||||||
|
bright: boolean = true;
|
||||||
|
/** Delimiter between message arguments, default to space */
|
||||||
|
join: string = ' ';
|
||||||
|
|
||||||
|
/** Control when logs are handled for this source - similar (defaults to) `LoggingConfiguration.logTiming` */
|
||||||
|
logTiming: LogTiming = LoggingConfiguration.logTiming;
|
||||||
|
/** Control how time is displayed for this source - similar (defaults to) `LoggingConfiguration.timeFormat` */
|
||||||
|
timeFormat: TimeFormat = LoggingConfiguration.timeFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a logging source
|
||||||
|
* ```ts
|
||||||
|
* const log = new Logging("Main");
|
||||||
|
*
|
||||||
|
* log.i("Hello World!");
|
||||||
|
* ```
|
||||||
|
* @param source Module identifier. Used in every line to identify the module that sent the message.
|
||||||
|
* @param silent Set to false to log a message when the logger instantiates. May be useful when debugging.
|
||||||
|
* @returns A source for logging messages to the console (stdout). Functions for info, warnings, errors, debug statements, and network events are provided and have shorthands.
|
||||||
|
*/
|
||||||
|
constructor(source: string, options?: LoggingOptions) {
|
||||||
|
this.visible = true;
|
||||||
|
this.source = source;
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if (options.bright) this.bright = options.bright;
|
||||||
|
if (typeof options.silent == 'boolean' && !options.silent) this.info(`Instantiated logging for ${this.source}`);
|
||||||
|
if (options.logTiming) this.logTiming = options.logTiming;
|
||||||
|
if (options.timeFormat) this.timeFormat = options.timeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggingConfiguration.sources.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #conversions(...msgs: Message[]) {
|
||||||
|
return await Promise.all<string>(msgs.map(async msg => {
|
||||||
|
for (const converter of LoggingConfiguration.getConversions()
|
||||||
|
.values()
|
||||||
|
.toArray()
|
||||||
|
.sort((a, b) => -(typeof a.priority == 'undefined' ? 0 : a.priority) + (typeof b.priority == 'undefined' ? 0 : b.priority))
|
||||||
|
)
|
||||||
|
// condition may or may not be async
|
||||||
|
if (await Promise.resolve(converter.condition(msg))) return await converter.converter(msg);
|
||||||
|
|
||||||
|
return String(msg); // fallback to JS string
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#log(type: MessageType, ...msgs: Message[]) {
|
||||||
|
if (!this.visible) return;
|
||||||
|
const func = async () => {
|
||||||
|
|
||||||
|
const time = new Date();
|
||||||
|
|
||||||
|
const str = (await this.#conversions(...msgs)).join(this.join);
|
||||||
|
|
||||||
|
const timeFormatted = ANSI.Colors.dim(Logging.timeStr(this.timeFormat, time));
|
||||||
|
|
||||||
|
let color: number;
|
||||||
|
switch (type) {
|
||||||
|
case MessageType.Info:
|
||||||
|
color = this.bright ? ANSI.Colors.BRIGHT_WHITE : ANSI.Colors.WHITE;
|
||||||
|
break;
|
||||||
|
case MessageType.Warn:
|
||||||
|
color = this.bright ? ANSI.Colors.BRIGHT_YELLOW : ANSI.Colors.YELLOW;
|
||||||
|
break;
|
||||||
|
case MessageType.Error:
|
||||||
|
color = this.bright ? ANSI.Colors.BRIGHT_RED : ANSI.Colors.RED;
|
||||||
|
break;
|
||||||
|
case MessageType.Debug:
|
||||||
|
color = this.bright ? ANSI.Colors.BRIGHT_GREEN : ANSI.Colors.GREEN;
|
||||||
|
break;
|
||||||
|
case MessageType.Network:
|
||||||
|
color = this.bright ? ANSI.Colors.BRIGHT_CYAN : ANSI.Colors.CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const targetStr = `${ANSI.Colors.inverse(ANSI.Colors.fg(`${this.source} ${Logging.typeStr(type)}`, color))} ${ANSI.Colors.fg(str, color)}`;
|
||||||
|
const msg = `${timeFormatted}${targetStr}`;
|
||||||
|
|
||||||
|
process.stdout.write(`${msg}${Logging.getNewline()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoggingListeners.emit('basic', msg);
|
||||||
|
LoggingListeners.emit('type', str, type, this.source, time);
|
||||||
|
} catch (err) {
|
||||||
|
process.stderr.write(`(FALLBACK ERROR) Callback failed! Stack: ${(err as Error).stack}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.logTiming) {
|
||||||
|
case LogTiming.Sync:
|
||||||
|
func();
|
||||||
|
break;
|
||||||
|
case LogTiming.Deferred:
|
||||||
|
setImmediate(func);
|
||||||
|
break;
|
||||||
|
case LogTiming.Microtask:
|
||||||
|
if (typeof queueMicrotask == 'function')
|
||||||
|
queueMicrotask(func);
|
||||||
|
else
|
||||||
|
setTimeout(func, 0); // fallback
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
func();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type The kind of message to log
|
||||||
|
* @param msgs Your message(s)
|
||||||
|
*/
|
||||||
|
log(type: MessageType, ...msgs: Message[]) {
|
||||||
|
switch (type) {
|
||||||
|
case MessageType.Info:
|
||||||
|
this.info(...msgs);
|
||||||
|
break;
|
||||||
|
case MessageType.Warn:
|
||||||
|
this.warn(...msgs);
|
||||||
|
break;
|
||||||
|
case MessageType.Error:
|
||||||
|
this.error(...msgs);
|
||||||
|
break;
|
||||||
|
case MessageType.Debug:
|
||||||
|
this.debug(...msgs);
|
||||||
|
break;
|
||||||
|
case MessageType.Network:
|
||||||
|
this.network(...msgs);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.info(...msgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logging Function */
|
||||||
|
i(...msgs: Message[]) { this.info(...msgs); }
|
||||||
|
/** Logging Function */
|
||||||
|
info(...msgs: Message[]) {
|
||||||
|
if (!LoggingConfiguration.MessageTypeVisibility.Info) return;
|
||||||
|
this.#log(MessageType.Info, ...msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logging Function */
|
||||||
|
w(...msgs: Message[]) { this.warn(...msgs); }
|
||||||
|
/** Logging Function */
|
||||||
|
warn(...msgs: Message[]) {
|
||||||
|
if (!LoggingConfiguration.MessageTypeVisibility.Warn) return;
|
||||||
|
this.#log(MessageType.Warn, ...msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logging Function */
|
||||||
|
e(...msgs: Message[]) { this.error(...msgs); }
|
||||||
|
/** Logging Function */
|
||||||
|
error(...msgs: Message[]) {
|
||||||
|
if (!LoggingConfiguration.MessageTypeVisibility.Error) return;
|
||||||
|
this.#log(MessageType.Error, ...msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logging Function */
|
||||||
|
d(...msgs: Message[]) { this.debug(...msgs); }
|
||||||
|
/** Logging Function */
|
||||||
|
debug(...msgs: Message[]) {
|
||||||
|
if (!LoggingConfiguration.MessageTypeVisibility.Debug) return;
|
||||||
|
this.#log(MessageType.Debug, ...msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logging Function */
|
||||||
|
n(...msgs: Message[]) { this.network(...msgs); }
|
||||||
|
/** Logging Function */
|
||||||
|
network(...msgs: Message[]) {
|
||||||
|
if (!LoggingConfiguration.MessageTypeVisibility.Network) return;
|
||||||
|
this.#log(MessageType.Network, ...msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'On Message' event listeners
|
||||||
|
type BasicListener = (msg: string) => unknown;
|
||||||
|
type TypeListener = (msg: string, type: MessageType, source: string, time: Date) => unknown;
|
||||||
|
type Listener = BasicListener | TypeListener;
|
||||||
|
type ListenerType = 'basic' | 'type';
|
||||||
|
|
||||||
|
interface InternalListener {
|
||||||
|
cb: Listener,
|
||||||
|
type: ListenerType
|
||||||
|
}
|
||||||
|
export class ListenersBase {
|
||||||
|
|
||||||
|
listeners: Set<InternalListener> = new Set<InternalListener>();
|
||||||
|
|
||||||
|
/** Register listener callback */
|
||||||
|
onmsg(type: 'basic', cb: BasicListener): void
|
||||||
|
onmsg(type: 'type', cb: TypeListener): void
|
||||||
|
onmsg(type: ListenerType, cb: Listener) {
|
||||||
|
if (type == 'basic') this.listeners.add({ cb, type: "basic" });
|
||||||
|
else if (type == 'type') this.listeners.add({ cb, type: "type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove listener callback */
|
||||||
|
offmsg(type: 'basic', cb: BasicListener): void
|
||||||
|
offmsg(type: 'type', cb: TypeListener): void
|
||||||
|
offmsg(type: ListenerType, cb: Listener) {
|
||||||
|
if (type == 'basic') this.listeners.delete({ cb, type: "basic" });
|
||||||
|
else if (type == 'type') this.listeners.delete({ cb, type: "type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Emit a log line */
|
||||||
|
emit(type: 'basic', msg: string): void;
|
||||||
|
emit(type: 'type', msg: string, msgtype: MessageType, source: string, time: Date): void;
|
||||||
|
emit(type: ListenerType, msg: string, msgtype?: MessageType, source?: string, time?: Date) {
|
||||||
|
if (type == 'basic')
|
||||||
|
for (const cb of this.listeners.values().filter(listener => listener.type == 'basic')) (cb.cb as BasicListener)(msg);
|
||||||
|
if (type == 'type') {
|
||||||
|
if ([msgtype, source, time].some(val => typeof val == 'undefined')) throw new Error("Missing arguments for type log emission");
|
||||||
|
for (const cb of this.listeners.values().filter(listener => listener.type == "type")) (cb.cb as TypeListener)(msg, msgtype!, source!, time!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add/remove callbacks: run when something is logged anywhere
|
||||||
|
*/
|
||||||
|
const LoggingListeners: ListenersBase = new ListenersBase();
|
||||||
|
|
||||||
|
class LoggingConfigurationBase {
|
||||||
|
|
||||||
|
#logTiming: LogTiming = LogTiming.Sync;
|
||||||
|
|
||||||
|
#timeFormat: TimeFormat = TimeFormat.Utc;
|
||||||
|
|
||||||
|
sources: Set<Logging> = new Set();
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
#conversions: Set<Conversion<any>> = new Set(); // ¯\_(ツ)_/¯
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
this.addConversion<Error>({
|
||||||
|
condition: arg => arg instanceof Error,
|
||||||
|
converter: arg => arg.stack || arg.message
|
||||||
|
});
|
||||||
|
this.addConversion<null>({
|
||||||
|
condition: arg => arg == null,
|
||||||
|
converter: () => `null`
|
||||||
|
});
|
||||||
|
this.addConversion<undefined>({
|
||||||
|
condition: arg => arg == undefined,
|
||||||
|
converter: () => `undefined`
|
||||||
|
});
|
||||||
|
this.addConversion<Response>({
|
||||||
|
condition: arg => arg instanceof Response,
|
||||||
|
converter: arg => {
|
||||||
|
try {
|
||||||
|
const url = arg.url.toString().length == 0 ? '(unknown origin) ' : new URL(arg.url);
|
||||||
|
const statusText = arg.statusText.length > 0 ? `${arg.statusText} ` : '';
|
||||||
|
|
||||||
|
const shouldNewline = Array.from(arg.headers.keys()).length > 0;
|
||||||
|
const entries = Array.from(arg.headers.entries());
|
||||||
|
return (
|
||||||
|
`${arg.status} ${statusText}${url}${shouldNewline ? '\n ' : ''}` +
|
||||||
|
entries.map(val => `${val[0]}: ${val[1]}`).join(`\n `)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.addConversion<Request>({
|
||||||
|
condition: arg => arg instanceof Request,
|
||||||
|
converter: arg => {
|
||||||
|
try {
|
||||||
|
const url = new URL(arg.url);
|
||||||
|
const shouldNewline = Array.from(arg.headers.keys()).length > 0;
|
||||||
|
const entries = Array.from(arg.headers.entries());
|
||||||
|
return `${arg.method} ${url}${shouldNewline ? '\n ' : ''}${entries.map(val => `${val[0]}: ${val[1]}`).join(`\n `)}`;
|
||||||
|
} catch {
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConversions() {
|
||||||
|
this.#conversions.clear();
|
||||||
|
}
|
||||||
|
addConversion<T>(con: Conversion<T>) {
|
||||||
|
this.#conversions.add(con);
|
||||||
|
}
|
||||||
|
getConversions(): Set<Conversion> {
|
||||||
|
return this.#conversions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllLoggers(): Logging[] {
|
||||||
|
return this.sources.values().toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
get timeFormat(): TimeFormat { return this.#timeFormat }
|
||||||
|
set timeFormat(data: TimeFormat) { this.#timeFormat = data }
|
||||||
|
|
||||||
|
get logTiming(): LogTiming { return this.#logTiming; }
|
||||||
|
set logTiming(data: LogTiming) { this.#logTiming = data }
|
||||||
|
|
||||||
|
set resetTimeFormat(data: TimeFormat) { for (const source of this.sources.values()) source.timeFormat = data; this.#timeFormat = data; }
|
||||||
|
set resetLogTiming(data: LogTiming) { for (const source of this.sources.values()) source.logTiming = data; this.#logTiming = data; }
|
||||||
|
|
||||||
|
/** Enable/disable logging of certain message types across all logging sources */
|
||||||
|
MessageTypeVisibility = {
|
||||||
|
Info: true,
|
||||||
|
Warn: true,
|
||||||
|
Error: true,
|
||||||
|
Debug: true,
|
||||||
|
Network: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Configure global logging configuration, such as converters and visibility
|
||||||
|
*/
|
||||||
|
const LoggingConfiguration: LoggingConfigurationBase = new LoggingConfigurationBase();
|
||||||
|
|
||||||
|
export { MessageType, TimeFormat, LogTiming, LoggingListeners, LoggingConfiguration };
|
||||||
|
export default Logging;
|
||||||
74
src/types.ts
Normal file
74
src/types.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Specify when, in/around the event loop, logs are sent
|
||||||
|
*/
|
||||||
|
export enum LogTiming {
|
||||||
|
Sync,
|
||||||
|
Deferred,
|
||||||
|
Microtask
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Specify the format that loggers use to display time
|
||||||
|
*/
|
||||||
|
export enum TimeFormat {
|
||||||
|
None,
|
||||||
|
Utc,
|
||||||
|
Unix,
|
||||||
|
Local,
|
||||||
|
RoundedLocal
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'On Message' event listeners
|
||||||
|
export type BasicListener = (msg: string) => unknown;
|
||||||
|
export type TypeListener = (msg: string, type: MessageType, source: string, time: Date) => unknown;
|
||||||
|
export type Listener = BasicListener | TypeListener;
|
||||||
|
export type ListenerType = 'basic' | 'type';
|
||||||
|
|
||||||
|
/** Useful for conditional logging with the `log` function */
|
||||||
|
export enum MessageType {
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
Debug,
|
||||||
|
Network
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Type for any object given to a logging function. Not really much different from `any`. */
|
||||||
|
export type Message = null | undefined | boolean | number | bigint | string | symbol | object;
|
||||||
|
|
||||||
|
/** Convert objects of any certain kind of value to a string. */
|
||||||
|
export interface Conversion<T = Message> {
|
||||||
|
/**
|
||||||
|
* Given a message value, determine whether this converter is capable of converting to a string or not.
|
||||||
|
*
|
||||||
|
* Promises are honored.
|
||||||
|
*/
|
||||||
|
condition: (arg: unknown) => Promise<boolean> | boolean,
|
||||||
|
/**
|
||||||
|
* Runtime type of object is guaranteed when the condition passes.
|
||||||
|
*
|
||||||
|
* Promises are honored.
|
||||||
|
*/
|
||||||
|
converter: (arg: T) => Promise<string> | string,
|
||||||
|
/**
|
||||||
|
* Defaults to 0.
|
||||||
|
*
|
||||||
|
* Since converters are checked sequentially in order of priority, generic converters should have higher priority.
|
||||||
|
*
|
||||||
|
* Converters with a priority closer to 0 have a higher priority.
|
||||||
|
*
|
||||||
|
* For example, when:
|
||||||
|
* * a converter that converts `Error` with priority 39, and
|
||||||
|
* * a converter that converts `CustomError` with priority 38,
|
||||||
|
*
|
||||||
|
* priority 1 will be resolved first to determine converter compatibility with the message value.
|
||||||
|
*/
|
||||||
|
priority?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How would you like your logs served? */
|
||||||
|
export interface LoggingOptions {
|
||||||
|
logTiming?: LogTiming,
|
||||||
|
timeFormat?: TimeFormat,
|
||||||
|
bright?: boolean,
|
||||||
|
silent?: boolean
|
||||||
|
}
|
||||||
7
tests/bright.ts
Normal file
7
tests/bright.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Logging from "../src/mod.ts";
|
||||||
|
|
||||||
|
const log = new Logging("NotBright");
|
||||||
|
|
||||||
|
log.bright = false;
|
||||||
|
|
||||||
|
log.i("Not Bright");
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
import Logging, { LoggingListeners } from "../mod.ts";
|
import Logging, { LoggingConfiguration, LoggingListeners } from "../src/mod.ts";
|
||||||
import process from "node:process";
|
|
||||||
|
|
||||||
const debug = process.argv[process.argv.length - 1] == 'true';
|
const debug = Deno.args[Deno.args.length - 1] === 'true';
|
||||||
console.debug(`Debug mode: ${debug}`);
|
console.debug(`Debug mode: ${debug}`);
|
||||||
const changeTimeFormat = process.argv[process.argv.length - 2] == 'true';
|
|
||||||
console.debug(`changeTimeFormat: ${changeTimeFormat}`);
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
LoggingListeners.onmsg('basic', msg => {
|
LoggingListeners.onmsg('basic', msg => {
|
||||||
console.debug(`\r\n[d] ${msg}`);
|
console.debug(`[d] ${msg}`);
|
||||||
});
|
});
|
||||||
LoggingListeners.onmsg('type', (msg, type, source, time) => {
|
LoggingListeners.onmsg('type', (msg, type, source, time) => {
|
||||||
console.debug(`[D] M:'${msg}' T:${type} S:'${source}' TM:${time.getTime()}`);
|
console.debug(`[D] M:'${msg}' T:${type} S:'${source}' TM:${time.getTime()}${Logging.getNewline()}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,4 +16,25 @@ const webLog = new Logging("Web");
|
|||||||
webLog.d(`Following is a Request`);
|
webLog.d(`Following is a Request`);
|
||||||
webLog.i(new Request('http://example.com?hello=world', { headers: { 'key1': 'value1', 'key2': 'value2' }}));
|
webLog.i(new Request('http://example.com?hello=world', { headers: { 'key1': 'value1', 'key2': 'value2' }}));
|
||||||
webLog.d(`Following is a Response`);
|
webLog.d(`Following is a Response`);
|
||||||
webLog.i(await fetch('https://example.com'));
|
webLog.i(await fetch('https://example.com'));
|
||||||
|
|
||||||
|
class CustomClass {
|
||||||
|
|
||||||
|
foo: string;
|
||||||
|
bar: number;
|
||||||
|
|
||||||
|
constructor(foo: string, bar: number) {
|
||||||
|
this.foo = foo;
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggingConfiguration.addConversion<CustomClass>({
|
||||||
|
condition: arg => arg instanceof CustomClass,
|
||||||
|
converter: arg => `CustomClass; foo:${arg.foo}; bar:${arg.bar};`
|
||||||
|
});
|
||||||
|
|
||||||
|
const log = new Logging("ConvertTest");
|
||||||
|
|
||||||
|
log.d(new CustomClass("baz", 39));
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Logging, { LoggingConfiguration, LogTiming } from "@proxnet/undead-logging";
|
import Logging, { LoggingConfiguration, LogTiming } from "./../src/mod.ts";
|
||||||
|
|
||||||
const log = new Logging("Logger");
|
const log = new Logging("Logger");
|
||||||
|
|
||||||
|
|||||||
28
tests/priority.ts
Normal file
28
tests/priority.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Logging, { LoggingConfiguration } from "@proxnet/undead-logging";
|
||||||
|
|
||||||
|
class CustomError extends Error {
|
||||||
|
|
||||||
|
someProperty: string
|
||||||
|
|
||||||
|
constructor(someProp: string) {
|
||||||
|
super("Something went wrong.");
|
||||||
|
this.someProperty = someProp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggingConfiguration.clearConversions();
|
||||||
|
LoggingConfiguration.addConversion<CustomError>({
|
||||||
|
condition: arg => arg instanceof CustomError,
|
||||||
|
converter: arg => `CustomError: someProperty:${arg.someProperty}; ${arg.stack || arg.message}`,
|
||||||
|
priority: -1
|
||||||
|
});
|
||||||
|
LoggingConfiguration.addConversion<Error>({
|
||||||
|
condition: arg => arg instanceof Error,
|
||||||
|
converter: arg => `Error: ${arg.stack || arg.message}`,
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const log = new Logging("PriorityTest");
|
||||||
|
|
||||||
|
log.i(new CustomError("'Hello World!'"));
|
||||||
8
tests/time.ts
Normal file
8
tests/time.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import Logging, { LoggingConfiguration } from "../src/mod.ts";
|
||||||
|
import { TimeFormat } from "../src/types.ts";
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.None;
|
||||||
|
|
||||||
|
const log = new Logging("NoDate");
|
||||||
|
|
||||||
|
log.i('No Date');
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Logging, {
|
import Logging, {
|
||||||
LoggingConfiguration,
|
LoggingConfiguration,
|
||||||
LogTiming,
|
LogTiming,
|
||||||
} from "../mod.ts";
|
} from "./../src/mod.ts";
|
||||||
|
|
||||||
LoggingConfiguration.logTiming = LogTiming.Microtask;
|
LoggingConfiguration.logTiming = LogTiming.Microtask;
|
||||||
// high-priority logs
|
// high-priority logs
|
||||||
|
|||||||
Reference in New Issue
Block a user