Refactor logger

This commit is contained in:
Evert Prants 2024-04-07 21:26:00 +03:00
parent c444a03884
commit d2b58fac6f
Signed by: evert
GPG Key ID: 1688DA83D222D0B5

View File

@ -1,26 +1,78 @@
import util from 'util'; import { format } from 'util';
type LogType = 'info' | 'debug' | 'warn' | 'error';
/** /**
* Logger for all of Squeebot. Use this instead of console.log/warn/error! * Log level
*/
export enum LogLevel {
INFO,
WARN,
ERROR,
DEBUG,
}
export type LogListener = (
ltype: LogLevel,
...data: any[]
) => void | Promise<void>;
export type ConsoleFunction = (...data: any[]) => void;
export type ConsoleLogFunction = ConsoleFunction;
export type ConsoleWarnFunction = ConsoleFunction;
export type ConsoleErrorFunction = ConsoleFunction;
/**
* Logger for all of Squeebot. Use this instead of console.log/warn/error in your plugins!
*/ */
export class Logger { export class Logger {
private console = [console.log, console.warn, console.error]; /**
* The `console.*` functions used for logging convenience.
*
* Defaults to `console.log`, `console.warn`, `console.error`.
*
* Can really be anything that accepts the input for `node:util.format`.
*/
public console: [
ConsoleLogFunction,
ConsoleWarnFunction,
ConsoleErrorFunction
] = [console.log, console.warn, console.error];
constructor( /**
public timestamp = 'dd/mm/yy HH:MM:ss' * External Logger log event listeners.
) {} */
protected listeners: LogListener[] = [];
public dateFmt(date: Date) { constructor(public timestamp = 'dd/mm/yy HH:MM:ss') {}
return date.toISOString()
.replace(/T/, ' ') public formatDate(date: Date): string {
.replace(/\..+/, ''); return date.toISOString().replace(/T/, ' ').replace(/\..+/, '');
} }
/** /**
* Set node.js readline consideration * Add a logger listener, useful for redirecting logger output somewhere else.
* Will not await any Promises but it will catch unhandled rejections - please do not depend on this.
* @param fn Log listener
*/
public listen(fn: LogListener): void {
this.listeners.push(fn);
}
/**
* Remove a logger listener.
* @param fn Log listener
* @returns nothing
*/
public unlisten(fn: LogListener): void {
const inx = this.listeners.indexOf(fn);
if (inx === -1) return;
this.listeners.splice(inx, 1);
}
/**
* Set node.js readline consideration.
* @param rl Readline instance * @param rl Readline instance
* @see Logger.console - the array modified by this function
* @see Logger.resetConsole - the "undo" to this function
*/ */
public setReadline(rl: any): void { public setReadline(rl: any): void {
for (const index in this.console) { for (const index in this.console) {
@ -34,32 +86,41 @@ export class Logger {
} }
/** /**
* Write out to log * Reset output consoles to default. Useful for dynamically detaching readline.
* @param ltype Logger level * @see Logger.console
* @param data Data to log
*/ */
private write(ltype: LogType, ...data: any[]): void { public resetConsole() {
const message = []; this.console = [console.log, console.warn, console.error];
let cfunc = this.console[0];
if (this.timestamp) {
message.push(`[${this.dateFmt(new Date())}]`);
} }
switch (ltype) { /**
case 'info': * Write out to log
* @param logLevel Logger level
* @param data Data to log. Will be sent to `node:util.format`
* @see util.format
*/
public write(logLevel: LogLevel, ...data: any[]): void {
const message = [];
let outputFunction = this.console[0];
if (this.timestamp) {
message.push(`[${this.formatDate(new Date())}]`);
}
switch (logLevel) {
case LogLevel.INFO:
message.push('[ INFO]'); message.push('[ INFO]');
break; break;
case 'debug': case LogLevel.DEBUG:
message.push('[DEBUG]'); message.push('[DEBUG]');
break; break;
case 'warn': case LogLevel.WARN:
message.push('[ WARN]'); message.push('[ WARN]');
cfunc = this.console[1]; outputFunction = this.console[1];
break; break;
case 'error': case LogLevel.ERROR:
message.push('[ERROR]'); message.push('[ERROR]');
cfunc = this.console[2]; outputFunction = this.console[2];
break; break;
} }
@ -67,10 +128,13 @@ export class Logger {
let final = data[0]; let final = data[0];
if (data.length > 1) { if (data.length > 1) {
const fargs = data.slice(1); const fargs = data.slice(1);
final = util.format(data[0], ...fargs); final = format(data[0], ...fargs);
} }
message.push(final); message.push(final);
cfunc(...message);
// Notify listeners and output
this.notify(logLevel, ...message);
outputFunction(...message);
} }
/** /**
@ -79,7 +143,7 @@ export class Logger {
* See `console.log` for more information. * See `console.log` for more information.
*/ */
public log(...data: any[]): void { public log(...data: any[]): void {
this.write('info', ...data); this.write(LogLevel.INFO, ...data);
} }
/** /**
@ -88,7 +152,7 @@ export class Logger {
* See `console.warn` for more information. * See `console.warn` for more information.
*/ */
public warn(...data: any[]): void { public warn(...data: any[]): void {
this.write('warn', ...data); this.write(LogLevel.WARN, ...data);
} }
/** /**
@ -97,7 +161,7 @@ export class Logger {
* See `console.log` for more information. * See `console.log` for more information.
*/ */
public info(...data: any[]): void { public info(...data: any[]): void {
this.write('info', ...data); this.write(LogLevel.INFO, ...data);
} }
/** /**
@ -106,7 +170,7 @@ export class Logger {
* See `console.error` for more information. * See `console.error` for more information.
*/ */
public error(...data: any[]): void { public error(...data: any[]): void {
this.write('error', ...data); this.write(LogLevel.ERROR, ...data);
} }
/** /**
@ -115,7 +179,34 @@ export class Logger {
* See `console.log` for more information. * See `console.log` for more information.
*/ */
public debug(...data: any[]): void { public debug(...data: any[]): void {
this.write('debug', ...data); this.write(LogLevel.DEBUG, ...data);
}
/**
* Notify logger listeners about new lines. Catches uncaught errors.
* @param level Log level
* @param data Log data
*/
protected notify(level: LogLevel, ...data: any): void {
for (const listener of this.listeners) {
try {
const resp = listener.call(null, level, ...data);
// Catch Promise errors
if (resp instanceof Promise) {
resp.catch((err) =>
process.stderr.write(
`A Logger listener threw an unhandled rejection: ${err.stack}\r\n`
)
);
}
} catch (err) {
process.stderr.write(
`A Logger listener threw an unhandled error: ${
(err as Error).stack
}\r\n`
);
}
}
} }
} }