221 lines
5.2 KiB
TypeScript
221 lines
5.2 KiB
TypeScript
import { format } from 'util';
|
|
|
|
/**
|
|
* Log level
|
|
*/
|
|
export enum LogLevel {
|
|
INFO,
|
|
WARN,
|
|
ERROR,
|
|
DEBUG,
|
|
}
|
|
|
|
export type LogListener = (
|
|
logLevel: 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 {
|
|
/**
|
|
* 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];
|
|
|
|
/**
|
|
* External Logger log event listeners.
|
|
*/
|
|
protected listeners: LogListener[] = [];
|
|
|
|
constructor(public timestamp = 'dd/mm/yy HH:MM:ss') {}
|
|
|
|
public formatDate(date: Date): string {
|
|
return date.toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @see Logger.console - the array modified by this function
|
|
* @see Logger.resetConsole - the "undo" to this function
|
|
*/
|
|
public setReadline(rl: any): void {
|
|
for (const index in this.console) {
|
|
const old = this.console[index];
|
|
this.console[index] = (...data: any[]): void => {
|
|
rl.output.write('\x1b[2K\r');
|
|
old(...data);
|
|
rl.prompt(true);
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset output consoles to default. Useful for dynamically detaching readline.
|
|
* @see Logger.console
|
|
*/
|
|
public resetConsole() {
|
|
this.console = [console.log, console.warn, console.error];
|
|
}
|
|
|
|
/**
|
|
* 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]');
|
|
break;
|
|
case LogLevel.DEBUG:
|
|
message.push('[DEBUG]');
|
|
break;
|
|
case LogLevel.WARN:
|
|
message.push('[ WARN]');
|
|
outputFunction = this.console[1];
|
|
break;
|
|
case LogLevel.ERROR:
|
|
message.push('[ERROR]');
|
|
outputFunction = this.console[2];
|
|
break;
|
|
}
|
|
|
|
// Short dance to apply formatting
|
|
let final = data[0];
|
|
if (data.length > 1) {
|
|
const fargs = data.slice(1);
|
|
final = format(data[0], ...fargs);
|
|
}
|
|
message.push(final);
|
|
|
|
// Notify listeners and output
|
|
this.notify(logLevel, ...message);
|
|
outputFunction(...message);
|
|
}
|
|
|
|
/**
|
|
* Logger level: `INFO`
|
|
*
|
|
* See `console.log` for more information.
|
|
*/
|
|
public log(...data: any[]): void {
|
|
this.write(LogLevel.INFO, ...data);
|
|
}
|
|
|
|
/**
|
|
* Logger level: `WARN`
|
|
*
|
|
* See `console.warn` for more information.
|
|
*/
|
|
public warn(...data: any[]): void {
|
|
this.write(LogLevel.WARN, ...data);
|
|
}
|
|
|
|
/**
|
|
* Logger level: `INFO`
|
|
*
|
|
* See `console.log` for more information.
|
|
*/
|
|
public info(...data: any[]): void {
|
|
this.write(LogLevel.INFO, ...data);
|
|
}
|
|
|
|
/**
|
|
* Logger level: `ERROR`
|
|
*
|
|
* See `console.error` for more information.
|
|
*/
|
|
public error(...data: any[]): void {
|
|
this.write(LogLevel.ERROR, ...data);
|
|
}
|
|
|
|
/**
|
|
* Logger level: `DEBUG`
|
|
*
|
|
* See `console.log` for more information.
|
|
*/
|
|
public debug(...data: any[]): void {
|
|
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`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create singleton for Logger to be used anywhere
|
|
/**
|
|
* Logger for all of Squeebot. Use this instead of console.log/warn/error in your plugins!
|
|
*
|
|
* This is a global singleton.
|
|
*/
|
|
const logger = new Logger();
|
|
export { logger };
|