diff --git a/src/Defaults.ts b/src/Defaults.ts index c72ff62..e88b223 100644 --- a/src/Defaults.ts +++ b/src/Defaults.ts @@ -10,10 +10,10 @@ enum LogLevel { export { LogLevel }; const ColourCodes: { [key: string]: number } = { - error: 0xe88388, - warn: 0xf9d472, - info: 0x76a9d8, - debug: 0xd8abd7, + error: 0xe88388, + warn: 0xf9d472, + info: 0x76a9d8, + debug: 0xd8abd7, status: 0x72d4d7 }; @@ -30,10 +30,10 @@ const TypeStream = { // If none defined they are sent to default }; const Colours = { - error: 'red', - warn: 'yellow', - info: 'blue', - debug: 'magenta', + error: 'red', + warn: 'yellow', + info: 'blue', + debug: 'magenta', status: 'cyanBright' }; @@ -55,11 +55,11 @@ type LogLevelType = { } const logLevelMapping = { - debug: LogLevel.debug, - info: LogLevel.info, + debug: LogLevel.debug, + info: LogLevel.info, status: LogLevel.status, - warn: LogLevel.warn, - error: LogLevel.error + warn: LogLevel.warn, + error: LogLevel.error }; type SharedOptionsType = { @@ -74,10 +74,10 @@ type SharedOptionsType = { const SharedOptions: SharedOptionsType = { guard, customStreams, - logLevel: LogLevel.info, + logLevel: LogLevel.info, logLevelMapping, customTypes: [], - labels: [] + labels: [] }; export type LoggerMasterOptions = SharedOptionsType & { @@ -94,11 +94,11 @@ export type LoggerMasterOptions = SharedOptionsType & { const MasterOptions: LoggerMasterOptions = { ...SharedOptions, fileRotationFreq: 1, - directory: './logs', + directory: './logs', customTypes, customTypeMapping, customColours, - broadcastLevel: 4, + broadcastLevel: 4, webhook, pruneDays, skipFileWrite diff --git a/src/LoggerClient.ts b/src/LoggerClient.ts index 177850d..51653db 100644 --- a/src/LoggerClient.ts +++ b/src/LoggerClient.ts @@ -30,7 +30,7 @@ class LoggerClient implements Logger #types: string[]; #labels: string[]; - constructor (opts: LoggerClientOptions = Defaults.ClientOptions) + constructor (opts: LoggerClientOptions = Defaults.ClientOptions) { this.#_name = opts.name || opts.constructor.name; if (this.#_name === 'Object') @@ -61,17 +61,17 @@ class LoggerClient implements Logger } } - get logLevel () + get logLevel () { return this.#_logLevel; } - get logLevels () + get logLevels () { return Object.keys(this.#_logLevelMapping); } - setLogLevel (level = 'info') + setLogLevel (level = 'info') { if (typeof level === 'number') this.#_logLevel = level; @@ -81,7 +81,7 @@ class LoggerClient implements Logger throw new Error(`Invalid log level type, expected string or number, got ${typeof level}`); } - #transport (type = 'info', ...args: [...entries: Loggable[], options: TransportOptions]) + #transport (type = 'info', ...args: [...entries: Loggable[], options: TransportOptions]) { if (this.#_logLevelMapping[type] < this.#_logLevel) return; @@ -102,7 +102,7 @@ class LoggerClient implements Logger message += inspect(makePlainError(entry)) + ' '; else if (typeof entry === 'string' || typeof entry === 'number') message += entry + ' '; - else + else message += inspect(entry) + ' '; } @@ -117,28 +117,28 @@ class LoggerClient implements Logger } // These methods are dynamically implemented by the constructor, simply here to provide IDE hints - // eslint-disable-next-line @typescript-eslint/no-unused-vars - error (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void + + error (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - warn (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void + + warn (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - status (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void + + status (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - info (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void + + info (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - debug (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void + + debug (..._args: [...entries: Loggable[], options: WriteOptions|Loggable]): void { throw new Error('Method not implemented.'); } diff --git a/src/MasterLogger.ts b/src/MasterLogger.ts index e42ffc2..d310da4 100644 --- a/src/MasterLogger.ts +++ b/src/MasterLogger.ts @@ -28,7 +28,7 @@ type WriteStreams = { [key:string]: fs.WriteStream } -class MasterLogger implements Logger +class MasterLogger implements Logger { [key: string]: LogFunction | unknown; @@ -54,7 +54,7 @@ class MasterLogger implements Logger #skipFileWrite: boolean; #labels: string[]; - constructor (config = Defaults.MasterOptions) + constructor (config = Defaults.MasterOptions) { const { directory, customTypes = [], customStreams = [], customTypeMapping, @@ -62,17 +62,17 @@ class MasterLogger implements Logger webhook, broadcastLevel, pruneDays, skipFileWrite, labels = [] } = { ...Defaults.MasterOptions, ...config }; - if (!directory) + if (!directory) throw new Error('Missing directory for log files'); this.#directory = path.resolve(directory); - if (!fs.existsSync(this.#directory)) + if (!fs.existsSync(this.#directory)) fs.mkdirSync(this.#directory, { recursive: true }); - + this.#_broadcastLevel = broadcastLevel as number; this.#_logLevel = logLevel as number; this.#_logLevelMapping = { ...Defaults.MasterOptions.logLevelMapping, ...logLevelMapping }; - if (logLevelMapping) - Object.entries(logLevelMapping).forEach(([ name, level ]) => + if (logLevelMapping) + Object.entries(logLevelMapping).forEach(([ name, level ]) => { addLogLevel(name, level); }); @@ -80,7 +80,7 @@ class MasterLogger implements Logger this.#skipFileWrite = skipFileWrite as boolean; this.#types = [ ...customTypes, ...Defaults.Types ]; - for (const type of this.#types) + for (const type of this.#types) { if (typeof this.#_logLevelMapping[type] === 'undefined') throw new Error(`Missing logLevelMapping for type ${type}`); @@ -90,8 +90,8 @@ class MasterLogger implements Logger }); } this.#colours = { ...Defaults.Colours, ...customColours }; - - this.#colourFuncs = Object.entries(this.#colours).reduce((prev: FuncsType, [ type, colour ]: (string | number)[]) => + + this.#colourFuncs = Object.entries(this.#colours).reduce((prev: FuncsType, [ type, colour ]: (string | number)[]) => { if (typeof colour === 'number') colour = `#${colour.toString(16)}`; @@ -104,7 +104,7 @@ class MasterLogger implements Logger // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore prev[type] = { func: chalk[colour], int: Defaults.ColourCodes[type as string] || Defaults.ColourCodes.info }; - + return prev; }, {} as FuncsType); @@ -141,68 +141,68 @@ class MasterLogger implements Logger clearInterval(this.#pruneInterval); const streams = Object.keys(this.#writeStreams); - for (const type of streams) + for (const type of streams) this.#writeStreams[type].end(); clearTimeout(this.#rotateTO); } - get guard () + get guard () { return this.#_guard; } - get logLevel () + get logLevel () { return this.#_logLevel; } - get logLevels () + get logLevels () { return Object.keys(this.#_logLevelMapping); } - pruneLogFiles () + pruneLogFiles () { const directory = fs.readdirSync(this.#directory, { withFileTypes: true }); const now = Date.now(); const limit = this.#pruneDays * 24 * 60 * 60 * 1000; - for (const entry of directory) + for (const entry of directory) { - if (!entry.isFile() || !(/\d{4}-\d{2}-\d{2}-[a-z]+\.log/u).test(entry.name)) + if (!entry.isFile() || !(/\d{4}-\d{2}-\d{2}-[a-z]+\.log/iu).test(entry.name)) continue; const [ year, month, day ] = entry.name.split('-'); const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day)).getTime(); - if ((now - date) < limit) + if ((now - date) < limit) continue; fs.unlinkSync(path.join(this.#directory, entry.name)); } } - setLogLevel (level: number) + setLogLevel (level: number) { - if (LogLevel[level] in this.#_logLevelMapping) + if (LogLevel[level] in this.#_logLevelMapping) this.#_logLevel = level; - else + else throw new Error('Not a valid log level'); } - setBroadcastLevel (level: number) + setBroadcastLevel (level: number) { - if (LogLevel[level] in this.#_logLevelMapping) + if (LogLevel[level] in this.#_logLevelMapping) this.#_broadcastLevel = level; - else + else throw new Error('Not a valid log level'); } - attach (shard: Shard) + attach (shard: Shard) { - shard.on('message', (msg: IPCMessage) => + shard.on('message', (msg: IPCMessage) => { - if (!msg[this.#_guard]) + if (!msg[this.#_guard]) return; const { message, type, header, broadcast, labels } = msg; const func = this[type] as LogFunction; - if (!func) + if (!func) throw new Error(`Attempted use of invalid logging function of type: ${type}, ensure client and master have the same type definitions.`); func(message, { subheader: header, shard, broadcast, labels }); }); @@ -214,22 +214,22 @@ class MasterLogger implements Logger let { subheader = '', shard, broadcast = false, labels = [] }: WriteOptions = {}; if (typeof last === 'object' && isWriteOptions(last)) { - ({ subheader = '', shard, broadcast = false, labels =[] } = last); + ({ subheader = '', shard, broadcast = false, labels =[] } = last); args.pop(); } - + let colour = this.#colourFuncs[type]; - if (!colour) + if (!colour) colour = this.#colourFuncs.info; let text = ''; for (const entry of args as Loggable[]) { - if (entry instanceof Error) + if (entry instanceof Error) text += inspect(makePlainError(entry)) + ' '; else if (typeof entry === 'string' || typeof entry === 'number') text += entry + ' '; - else + else text += inspect(text) + ' '; } text = text.trim(); @@ -238,107 +238,108 @@ class MasterLogger implements Logger const maxChars = Math.max(...this.#types.map(t => t.length)); const spacer = ' '.repeat(maxChars - type.length); - if (this.#_logLevelMapping[type] >= this.#_logLevel) + if (this.#_logLevelMapping[type] >= this.#_logLevel || process.env.NODE_ENV === 'development') { const out = `${colour.func(type)}${spacer} ${colour.func(header)}: ${chalk.bold(subheader)}${text}`; if (type === 'error') console.error(out); // eslint-disable-line no-console - else + else console.log(out); // eslint-disable-line no-console } - if ((broadcast || (this.#_broadcastLevel <= this.#_logLevelMapping[type])) && this.#webhook) + if ((broadcast || (this.#_broadcastLevel <= this.#_logLevelMapping[type])) && this.#webhook) { - const description = (subheader.length ? `**${subheader.trim()}**: ${process.env.NODE_ENV ?? 'production'}\n` : '') + `\`\`\`${text}\`\`\``; + const description = (subheader.length ? `**${subheader.trim()}**` : '**ENV**') + `: ${process.env.NODE_ENV ?? 'production'}`; + const content = `\`\`\`${text}\`\`\``; this.#webhook.send({ embeds: [{ - title: `[__${type.toUpperCase()}__] ${this._shard(shard)}`, - description, - color: colour.int, - footer: { + title: `[__${type.toUpperCase()}__] ${this._shard(shard)}`, + description: `${description}\n${content}`, + color: colour.int, + footer: { text: [ ...labels, ...this.#labels ].join(', ') ?? '' } }] - }); + }); } if (this.#skipFileWrite) return; const streamType = this.#streamTypeMapping[type] || 'default'; - if (this.#writeStreams[streamType]) + if (this.#writeStreams[streamType]) this.#writeStreams[streamType].write(`\n${type}${spacer} ${header}: ${subheader}${text}`); - else + else console.log(`${chalk.red(`[LOGGER] Missing file stream for ${streamType}`)}`); // eslint-disable-line no-console } - rotateLogFiles () + rotateLogFiles () { const streams = Object.keys(this.#writeStreams); - for (const type of streams) + for (const type of streams) { this.#writeStreams[type].write('\nRotating log file'); this.#writeStreams[type].end(); this.#writeStreams[type] = this.loadFile(type); } const nextTime = Math.floor(Date.now() / this.#rotationFreq) * this.#rotationFreq + this.#rotationFreq; - if (this.#rotateTO) + if (this.#rotateTO) clearTimeout(this.#rotateTO); this.#rotateTO = setTimeout(this.rotateLogFiles.bind(this), nextTime - Date.now()); } - loadFile (type: string, date = Date.now()) + loadFile (type: string, date = Date.now()) { - if (!type) + if (!type) throw new Error('Missing file type'); const fileName = `${moment(date).format('YYYY-MM-DD')}-${type}.log`; const filePath = path.join(this.#directory, fileName); - if (!fs.existsSync(filePath)) + if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, ''); return fs.createWriteStream(filePath, { flags: 'a' }); } - _shard (shard?: Shard) + _shard (shard?: Shard) { - if (!shard) + if (!shard) return 'controller'; let id = '??'; - if ('id' in shard) + if ('id' in shard) id = `${shard.id < 10 ? `0${shard.id}` : shard.id}`; return `shard-${id}`; } - get date () + get date () { return moment().format('YYYY-MM-DD HH:mm:ss'); } // These methods are dynamically implemented by the constructor - // eslint-disable-next-line @typescript-eslint/no-unused-vars - error (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void + + error (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - warn (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void + + warn (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - status (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void + + status (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - info (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void + + info (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void { throw new Error('Method not implemented.'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - debug (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void + + debug (..._args: [...entries: Loggable[], options: WriteOptions | Loggable]): void { throw new Error('Method not implemented.'); } diff --git a/src/Shared.ts b/src/Shared.ts index 44731cc..794b257 100644 --- a/src/Shared.ts +++ b/src/Shared.ts @@ -3,14 +3,14 @@ import { WriteOptions } from './Types'; export const makePlainError = (err: Error) => { return { - name: err.name, + name: err.name, message: err.message, - stack: err.stack + stack: err.stack }; }; const validKeys = [ 'subheader', 'shard', 'broadcast', 'labels' ]; -export const isWriteOptions = (obj: unknown, extended = false): obj is WriteOptions => +export const isWriteOptions = (obj: unknown, extended = false): obj is WriteOptions => { if (!obj || typeof obj !== 'object') return false; diff --git a/src/Types.ts b/src/Types.ts index 186ea60..eeb41d3 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -5,7 +5,7 @@ type Shard = { } & EventEmitter; type WriteOptions = { - subheader?: string, + subheader?: string, shard?: Shard, broadcast?: boolean, labels?: string[]