version 1.3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
test.ts
|
|
||||||
60
README.md
60
README.md
@@ -34,7 +34,7 @@ log.n("Something happened");
|
|||||||
|
|
||||||
Disable a type of message globally:
|
Disable a type of message globally:
|
||||||
```ts
|
```ts
|
||||||
import Logging, { MessageTypeVisibility } from "./mod.ts";
|
import Logging, { MessageTypeVisibility } from "@proxnet/undead-logging";
|
||||||
// .. logging source "Main" is in the scope
|
// .. logging source "Main" is in the scope
|
||||||
|
|
||||||
MessageTypeVisibility.Error = false;
|
MessageTypeVisibility.Error = false;
|
||||||
@@ -46,3 +46,61 @@ log.error("I am not visible");
|
|||||||
// output:
|
// output:
|
||||||
// 2024-11-14T01:21:40.350Z Main [INFO] I am visible
|
// 2024-11-14T01:21:40.350Z Main [INFO] I am visible
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Event Listeners
|
||||||
|
Event listeners are called when a message is logged on any logging source
|
||||||
|
|
||||||
|
An event callback can either receive messages as either:
|
||||||
|
* Whole lines, including formatting, colors, excluding newlines
|
||||||
|
or
|
||||||
|
* Individual `msg` (string), `type` (MessageType), `source` (string), and `time` (Date) arguments
|
||||||
|
|
||||||
|
Callback ran when a message is logged anywhere:
|
||||||
|
```ts
|
||||||
|
LoggingListeners.onmsg('basic', msg => {
|
||||||
|
// `msg` is a string containing the entire line w/ formatting
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Callback ran when a message is logged anywhere (this time components are split up):
|
||||||
|
```ts
|
||||||
|
LoggingListeners.onmsg('type', (msg, type, source, time) => {
|
||||||
|
// msg: string
|
||||||
|
// type: MessageType - import { MessageType } from "@proxnet/undead-logging";)
|
||||||
|
// source: string
|
||||||
|
// time: Date
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove callback:
|
||||||
|
```ts
|
||||||
|
const cb = msg => { /* do something with msg */ };
|
||||||
|
LoggingListeners.onmsg('basic', cb);
|
||||||
|
LoggingListeners.offmsg('basic', cb);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Time display modes
|
||||||
|
|
||||||
|
You can display three different formats for time:
|
||||||
|
* UTC
|
||||||
|
* Unix time (in milliseconds)
|
||||||
|
* Local [process] time (in seconds, from the `performance` API)
|
||||||
|
|
||||||
|
Set the time format:
|
||||||
|
```ts
|
||||||
|
import { LoggingConfiguration, TimeFormat } from "@proxnet/undead-logging";
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Local
|
||||||
|
// or
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Utc
|
||||||
|
```
|
||||||
|
|
||||||
|
## (advanced) Logging timing
|
||||||
|
|
||||||
|
You can control when log functions are executed using `LoggingConfiguration.logTiming`.
|
||||||
|
|
||||||
|
Logs are sent synchronously by default. You can optionally defer logs with `setImmediate` using `LogTiming.Deferred`
|
||||||
|
```ts
|
||||||
|
LoggingConfiguration.logTiming = LogTiming.Sync
|
||||||
|
LoggingConfiguration.logTiming = LogTiming.Deferred
|
||||||
|
```
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
{
|
{
|
||||||
|
"tasks": {
|
||||||
|
"debug": "deno run -A test.ts true",
|
||||||
|
"plain": "deno run -A test.ts"
|
||||||
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@1",
|
"@std/assert": "jsr:@std/assert@1",
|
||||||
"chalk": "npm:chalk@^5.3.0"
|
"chalk": "npm:chalk@^5.3.0"
|
||||||
},
|
},
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"name": "@proxnet/undead-logging",
|
"name": "@proxnet/undead-logging",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
196
mod.ts
196
mod.ts
@@ -1,14 +1,25 @@
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
import { setImmediate } from "node:timers";
|
||||||
|
|
||||||
|
type ChalkFunction = (...msgs: unknown[]) => string;
|
||||||
|
type PrimitiveItems = unknown[];
|
||||||
|
|
||||||
|
interface UnknownConversion<T> {
|
||||||
|
condition: (arg: unknown) => boolean;
|
||||||
|
converter: (arg: T) => string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source for pretty and cool and fun logging
|
* A source for pretty and cool and fun logging
|
||||||
*/
|
*/
|
||||||
class Logging {
|
class Logging {
|
||||||
|
|
||||||
/** Can I be seen by the console? */
|
/** Can I be seen by the console and listeners? */
|
||||||
visible: boolean
|
visible: boolean
|
||||||
/** What is my name? */
|
/** What is my name? */
|
||||||
source: string
|
source: string
|
||||||
|
/** Should I use bright colors over dull ones? */
|
||||||
|
bright: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a logging source
|
* Create a logging source
|
||||||
@@ -21,10 +32,90 @@ class Logging {
|
|||||||
* @param silent Set to false to log a message when the logger instantiates. Useful for debugging.
|
* @param silent Set to false to log a message when the logger instantiates. Useful for debugging.
|
||||||
* @returns A source for logging messages to the console. Functions for info, warnings, errors, debug statements, and network events are provided and have shorthands.
|
* @returns A source for logging messages to the console. Functions for info, warnings, errors, debug statements, and network events are provided and have shorthands.
|
||||||
*/
|
*/
|
||||||
constructor(source: string, silent?: boolean) {
|
constructor(source: string, silent?: boolean, bright?: boolean) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
if (typeof silent == 'boolean' && !silent) this.info(`Instantiated module logging`);
|
if (typeof bright == 'boolean') this.bright = bright;
|
||||||
|
|
||||||
|
if (typeof silent == 'boolean' && !silent) this.info(`Instantiated logging for ${this.source}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
#log(type: MessageType, chalkColor: ChalkFunction, ...msgs: PrimitiveItems) {
|
||||||
|
if (!this.visible) return;
|
||||||
|
const func = () => {
|
||||||
|
|
||||||
|
let typestr: string;
|
||||||
|
switch (type) {
|
||||||
|
case MessageType.Info:
|
||||||
|
typestr = '[INFO]';
|
||||||
|
break;
|
||||||
|
case MessageType.Warn:
|
||||||
|
typestr = '[WARN]';
|
||||||
|
break;
|
||||||
|
case MessageType.Error:
|
||||||
|
typestr = '[ERROR]';
|
||||||
|
break;
|
||||||
|
case MessageType.Debug:
|
||||||
|
typestr = '[DEBUG]';
|
||||||
|
break;
|
||||||
|
case MessageType.Network:
|
||||||
|
typestr = '[NETWORK]';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
typestr = '[UNKNOWN]';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = new Date();
|
||||||
|
let timeFormatted;
|
||||||
|
switch (LoggingConfiguration.timeFormat) {
|
||||||
|
case TimeFormat.Unix:
|
||||||
|
timeFormatted = time.getTime();
|
||||||
|
break;
|
||||||
|
case TimeFormat.Local:
|
||||||
|
timeFormatted = performance.now();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
timeFormatted = time.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => typeof arg == 'object',
|
||||||
|
converter: arg => JSON.stringify(arg)
|
||||||
|
} as UnknownConversion<object>,
|
||||||
|
];
|
||||||
|
function convertToString(arg: unknown) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = msgs.map(val => convertToString(val)).join(' ');
|
||||||
|
const msg = chalk.gray(`${timeFormatted} `) + chalkColor(chalk.inverse(`${this.source} ${typestr}`) + ` ${str}`);
|
||||||
|
console.log(msg);
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoggingListeners.emit('basic', msg);
|
||||||
|
LoggingListeners.emit('type', str, type, this.source, time);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`(FALLBACK ERROR) Callback failed! Stack: ${(err as Error).stack}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoggingConfiguration.logTiming == LogTiming.Sync) func();
|
||||||
|
else setImmediate(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +123,7 @@ class Logging {
|
|||||||
* @param type The kind of message to log
|
* @param type The kind of message to log
|
||||||
* @param msgs Your message(s)
|
* @param msgs Your message(s)
|
||||||
*/
|
*/
|
||||||
log(type: MessageType, ...msgs: string[]) {
|
log(type: MessageType, ...msgs: PrimitiveItems) {
|
||||||
if (type == MessageType.Info) this.i(...msgs);
|
if (type == MessageType.Info) this.i(...msgs);
|
||||||
if (type == MessageType.Warn) this.w(...msgs);
|
if (type == MessageType.Warn) this.w(...msgs);
|
||||||
if (type == MessageType.Error) this.e(...msgs);
|
if (type == MessageType.Error) this.e(...msgs);
|
||||||
@@ -41,48 +132,43 @@ class Logging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
i(...msgs: string[]) {this.info(...msgs);}
|
i(...msgs: PrimitiveItems) {this.info(...msgs);}
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
info(...msgs: string[]) {
|
info(...msgs: PrimitiveItems) {
|
||||||
if (!MessageTypeVisibility.Info) return;
|
if (!MessageTypeVisibility.Info) return;
|
||||||
if (this.visible !== true) return;
|
this.#log(MessageType.Info, chalk.whiteBright, ...msgs);
|
||||||
console.log(chalk.gray(`${new Date().toISOString()} `) + chalk.bgWhite.black(`${this.source} [INFO]`) + chalk.whiteBright(' ' + msgs.join(' ')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
w(...msgs: string[]) {this.warn(...msgs);}
|
w(...msgs: PrimitiveItems) {this.warn(...msgs);}
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
warn(...msgs: string[]) {
|
warn(...msgs: PrimitiveItems) {
|
||||||
if (!MessageTypeVisibility.Warn) return;
|
if (!MessageTypeVisibility.Warn) return;
|
||||||
if (this.visible !== true) return;
|
this.#log(MessageType.Warn, chalk.yellowBright, ...msgs);
|
||||||
console.warn(chalk.gray(`${new Date().toISOString()} `) + chalk.bgYellow.black(`${this.source} [WARN]`) + chalk.yellowBright(' ' + msgs.join(' ')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
e(...msgs: string[]) {this.error(...msgs);}
|
e(...msgs: PrimitiveItems) {this.error(...msgs);}
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
error(...msgs: string[]) {
|
error(...msgs: PrimitiveItems) {
|
||||||
if (!MessageTypeVisibility.Error) return;
|
if (!MessageTypeVisibility.Error) return;
|
||||||
if (this.visible !== true) return;
|
this.#log(MessageType.Error, chalk.redBright, ...msgs);
|
||||||
console.error(chalk.gray(`${new Date().toISOString()} `) + chalk.bgRed.black(`${this.source} [ERROR]`) + chalk.redBright(' ' + msgs.join(' ')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
d(...msgs: string[]) {this.debug(...msgs);}
|
d(...msgs: PrimitiveItems) {this.debug(...msgs);}
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
debug(...msgs: string[]) {
|
debug(...msgs: PrimitiveItems) {
|
||||||
if (!MessageTypeVisibility.Debug) return;
|
if (!MessageTypeVisibility.Debug) return;
|
||||||
if (this.visible !== true) return;
|
this.#log(MessageType.Debug, chalk.greenBright, ...msgs);
|
||||||
console.debug(chalk.gray(`${new Date().toISOString()} `) + chalk.bgGreen.black(`${this.source} [DEBUG]`) + chalk.greenBright(' ' + msgs.join(' ')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
n(...msgs: string[]) {this.network(...msgs);}
|
n(...msgs: PrimitiveItems) {this.network(...msgs);}
|
||||||
/** Logging Function */
|
/** Logging Function */
|
||||||
network(...msgs: string[]) {
|
network(...msgs: PrimitiveItems) {
|
||||||
if (!MessageTypeVisibility.Network) return;
|
if (!MessageTypeVisibility.Network) return;
|
||||||
if (this.visible !== true) return;
|
this.#log(MessageType.Network, chalk.cyanBright, ...msgs);
|
||||||
console.log(chalk.gray(`${new Date().toISOString()} `) + chalk.bgCyan.black(`${this.source} [NETWORK]`) + chalk.cyanBright(' ' + msgs.join(' ')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -105,5 +191,65 @@ const MessageTypeVisibility = {
|
|||||||
Network: true
|
Network: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export { MessageType, MessageTypeVisibility };
|
// '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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const LoggingListeners = new ListenersBase();
|
||||||
|
|
||||||
|
enum LogTiming {
|
||||||
|
Sync,
|
||||||
|
Deferred
|
||||||
|
}
|
||||||
|
enum TimeFormat {
|
||||||
|
Utc,
|
||||||
|
Unix,
|
||||||
|
Local
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoggingConfigurationBase {
|
||||||
|
|
||||||
|
#timing: LogTiming = LogTiming.Sync;
|
||||||
|
|
||||||
|
#timeType: TimeFormat = TimeFormat.Utc;
|
||||||
|
|
||||||
|
get timeFormat() { return this.#timeType }
|
||||||
|
set timeFormat(data) { this.#timeType = data }
|
||||||
|
|
||||||
|
get logTiming() { return this.#timing; }
|
||||||
|
set logTiming(data) { this.#timing = data }
|
||||||
|
|
||||||
|
}
|
||||||
|
const LoggingConfiguration = new LoggingConfigurationBase();
|
||||||
|
|
||||||
|
export { MessageType, TimeFormat, LogTiming, MessageTypeVisibility, LoggingListeners, LoggingConfiguration };
|
||||||
export default Logging;
|
export default Logging;
|
||||||
44
test.ts
Normal file
44
test.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import Logging, { LoggingListeners, LoggingConfiguration, MessageType, MessageTypeVisibility, TimeFormat, LogTiming } from "@proxnet/undead-logging";
|
||||||
|
import { setImmediate } from "node:timers/promises";
|
||||||
|
|
||||||
|
const debug = Deno.args[0] == 'true';
|
||||||
|
if (debug) {
|
||||||
|
LoggingListeners.onmsg('basic', msg => {
|
||||||
|
console.debug(`[d] ${msg}`);
|
||||||
|
});
|
||||||
|
LoggingListeners.onmsg('type', (msg, type, source, time) => {
|
||||||
|
console.debug(`[D] M:'${msg}' T:${type} S:'${source}' TM:${time.getTime()}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = new Logging("Test1");
|
||||||
|
|
||||||
|
log.i("Hello World!");
|
||||||
|
log.visible = false;
|
||||||
|
log.e('I should not be visible.');
|
||||||
|
log.visible = true;
|
||||||
|
log.e('Now I should be!');
|
||||||
|
|
||||||
|
const logg = new Logging("Test2");
|
||||||
|
|
||||||
|
logg.w(`Uh oh..`);
|
||||||
|
logg.d(`Here's some info that tells you about that warning:`, new Error('Uh oh..'));
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Unix;
|
||||||
|
logg.i(`Unix time!`);
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Local;
|
||||||
|
logg.i(`Process time!`);
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Utc;
|
||||||
|
LoggingConfiguration.logTiming = LogTiming.Deferred;
|
||||||
|
logg.log(MessageType.Network, "Deferred mode!");
|
||||||
|
|
||||||
|
LoggingConfiguration.timeFormat = TimeFormat.Local;
|
||||||
|
setImmediate(() => {
|
||||||
|
// all should be local mode
|
||||||
|
MessageTypeVisibility.Error = false;
|
||||||
|
logg.error("I can't be seen!");
|
||||||
|
log.error('Same!');
|
||||||
|
log.n("I *can* be seen!");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user