Log function improvements

Allow arbitrary number of arguments to be passed to logging fuctions
This commit is contained in:
Erik 2023-11-28 19:44:44 +02:00
parent 92731815e1
commit 2ab62a6368
5 changed files with 97 additions and 45 deletions

View File

@ -1,13 +1,20 @@
import Defaults, { LoggerClientOptions } from './Defaults.js';
import { inspect } from 'node:util';
import { LogFunction, WriteOptions } from './Types.js';
import { LogFunction, Loggable, WriteOptions } from './Types.js';
import { Logger } from './LoggerInterface.js';
import { makePlainError } from './Shared.js';
import { isWriteOptions, makePlainError } from './Shared.js';
type TransportOptions = {
type: string,
labels?: string[]
}
} & WriteOptions
const validKeys = [ 'labels' ];
const isTransportOpts = (obj: object): obj is TransportOptions =>
{
const isWriteOption = isWriteOptions(obj);
const keys = Object.keys(obj);
return isWriteOption || keys.some(key => validKeys.includes(key));
};
class LoggerClient implements Logger
{
@ -45,11 +52,11 @@ class LoggerClient implements Logger
if (typeof this.#_logLevelMapping[type] === 'undefined')
throw new Error(`Missing logLevelMapping for type ${type}`);
Object.defineProperty(this, type, {
value: (msg: string, o?: WriteOptions) =>
{
const { labels = [], ...writeOpts } = o ?? {};
this.#transport(msg, { ...writeOpts, type, labels: [ ...this.#labels, ...labels ] });
}
value: (...rest: [...entries: Loggable[], options: TransportOptions]) => this.#transport(type, ...rest)
// {
// const { labels = [], ...writeOpts } = o ?? {};
// this.#transport(msg, { ...writeOpts, type, labels: [ ...this.#labels, ...labels ] });
// }
});
}
}
@ -74,50 +81,64 @@ class LoggerClient implements Logger
throw new Error(`Invalid log level type, expected string or number, got ${typeof level}`);
}
#transport (message: string | object | Error, opts: TransportOptions)
#transport (type = 'info', ...args: [...entries: Loggable[], options: TransportOptions])
{
if (this.#_logLevelMapping[opts.type] < this.#_logLevel)
if (this.#_logLevelMapping[type] < this.#_logLevel)
return;
if (message instanceof Error)
message = makePlainError(message);
if (typeof message !== 'string')
message = inspect(message);
const last = args[args.length - 1];
let opts: TransportOptions = {};
if (typeof last === 'object' && isTransportOpts(last))
{
opts = last;
args.pop();
}
opts.labels = opts.labels ? [ ...opts.labels, ...this.#labels ] : this.#labels;
let message = '';
for (const entry of args as Loggable[])
{
if (entry instanceof Error)
message += inspect(makePlainError(entry)) + ' ';
else if (typeof entry === 'string' || typeof entry === 'number')
message += entry + ' ';
else
message += inspect(entry) + ' ';
}
const spacer = ' '.repeat(LoggerClient.MaxChars - this.#_name.length);
const header = `${`[${this.#_name.substring(0, LoggerClient.MaxChars)}]${spacer}`} `;
if (!process.send || !process.connected)
throw new Error('Missing connection to master proces');
else
process.send({ [this.#_guard]: true, header, message, ...opts });
process.send({ [this.#_guard]: true, type, header, message, ...opts });
}
// These methods are dynamically implemented by the constructor, simply here to provide IDE hints
// eslint-disable-next-line @typescript-eslint/no-unused-vars
error (_str: string | object | Error, _opts?: WriteOptions): void
error (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
warn (_str: string | object | Error, _opts?: WriteOptions): void
warn (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
status (_str: string | object | Error, _opts?: WriteOptions): void
status (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
info (_str: string | object | Error, _opts?: WriteOptions): void
info (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
debug (_str: string | object | Error, _opts?: WriteOptions): void
debug (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}

View File

@ -1,9 +1,9 @@
import { WriteOptions } from './Types';
import { Loggable, WriteOptions } from './Types';
export interface Logger {
error(str: string, opts?: WriteOptions): void
warn(str: string, opts?: WriteOptions): void
status(str: string, opts?: WriteOptions): void
info(str: string, opts?: WriteOptions): void
debug(str: string, opts?: WriteOptions): void
error(...args: [...entries: Loggable[], options: WriteOptions]): void
warn(...args: [...entries: Loggable[], options: WriteOptions]): void
status(...args: [...entries: Loggable[], options: WriteOptions]): void
info(...args: [...entries: Loggable[], options: WriteOptions]): void
debug(...args: [...entries: Loggable[], options: WriteOptions]): void
}

View File

@ -10,10 +10,10 @@ import { inspect } from 'node:util';
// Own
import DiscordWebhook from '@navy.gif/discord-webhook';
import Defaults, { LogLevel } from './Defaults.js';
import { IPCMessage, LogFunction, Shard, WriteOptions } from './Types.js';
import { IPCMessage, LogFunction, Loggable, Shard, WriteOptions } from './Types.js';
import { addLogLevel } from '../index.js';
import { Logger } from './LoggerInterface.js';
import { makePlainError } from './Shared.js';
import { isWriteOptions, makePlainError } from './Shared.js';
const DAY = 1000 * 60 * 60 * 24;
@ -85,7 +85,8 @@ class MasterLogger implements Logger
if (typeof this.#_logLevelMapping[type] === 'undefined')
throw new Error(`Missing logLevelMapping for type ${type}`);
Object.defineProperty(this, type, {
value: (msg: string, opts?: WriteOptions) => this.write(type, msg, opts)
// value: (msg: string, opts?: WriteOptions) => this.write(type, msg, opts)
value: (...rest: [...entries: Loggable[], options: WriteOptions]) => this.write(type, ...rest)
});
}
this.#colours = { ...Defaults.Colours, ...customColours };
@ -207,17 +208,31 @@ class MasterLogger implements Logger
});
}
write (type = 'info', text: string | object | Error, { subheader = '', shard, broadcast = false, labels = [] }: WriteOptions = {})
write (type = 'info', ...args: [...entries: Loggable[], options: WriteOptions])
{
const last = args[args.length - 1];
let { subheader = '', shard, broadcast = false, labels = [] }: WriteOptions = {};
if (typeof last === 'object' && isWriteOptions(last))
{
({ subheader = '', shard, broadcast = false, labels =[] } = last);
args.pop();
}
let colour = this.#colourFuncs[type];
if (!colour)
colour = this.#colourFuncs.info;
if (text instanceof Error)
text = makePlainError(text);
if (typeof text !== 'string')
text = inspect(text);
let text = '';
for (const entry of args as Loggable[])
{
if (entry instanceof Error)
text += inspect(makePlainError(entry)) + ' ';
else if (typeof entry === 'string' || typeof entry === 'number')
text += entry + ' ';
else
text += inspect(text) + ' ';
}
text = text.trim();
const header = `[${this.date}] [${this._shard(shard)}]`;
const maxChars = Math.max(...this.#types.map(t => t.length));
@ -233,7 +248,7 @@ class MasterLogger implements Logger
}
if ((broadcast || (this.#_broadcastLevel <= this.#_logLevelMapping[type])) && this.#webhook)
{
const description = (subheader.length ? `**${subheader}**: ${process.env.NODE_ENV ?? 'production'}\n` : '') + `\`\`\`${text}\`\`\``;
const description = (subheader.length ? `**${subheader.trim()}**: ${process.env.NODE_ENV ?? 'production'}\n` : '') + `\`\`\`${text}\`\`\``;
this.#webhook.send({
embeds: [{
title: `[__${type.toUpperCase()}__] ${this._shard(shard)}`,
@ -303,27 +318,27 @@ class MasterLogger implements Logger
// These methods are dynamically implemented by the constructor
// eslint-disable-next-line @typescript-eslint/no-unused-vars
error (_str: string | object | Error, _opts?: WriteOptions): void
error (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
warn (_str: string | object | Error, _opts?: WriteOptions): void
warn (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
status (_str: string | object | Error, _opts?: WriteOptions): void
status (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
info (_str: string | object | Error, _opts?: WriteOptions): void
info (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
debug (_str: string | object | Error, _opts?: WriteOptions): void
debug (..._args: [...entries: Loggable[], options: WriteOptions]): void
{
throw new Error('Method not implemented.');
}

View File

@ -1,3 +1,5 @@
import { WriteOptions } from './Types';
export const makePlainError = (err: Error) =>
{
return {
@ -5,4 +7,16 @@ export const makePlainError = (err: Error) =>
message: err.message,
stack: err.stack
};
};
const validKeys = [ 'subheader', 'shard', 'broadcast', 'labels' ];
export const isWriteOptions = (obj: object, extended = false): obj is WriteOptions =>
{
const keys = Object.keys(obj);
// Check for invalid keys, in some cases an arbitrary object might share keys
// while still allowing for an option to be extended
if (!extended && keys.some(key => !validKeys.includes(key)))
return false;
// Make sure it's not an empty object
return keys.some(key => validKeys.includes(key));
};

View File

@ -23,4 +23,6 @@ type IPCMessage = {
type LogFunction = (str: string, opts?: WriteOptions) => void
export { WriteOptions, Shard, IPCMessage, LogFunction };
type Loggable = string | number | object | Error
export { WriteOptions, Shard, IPCMessage, LogFunction, Loggable };