import { format } from 'util'; import { MongoInvalidArgumentError } from './error'; import { enumToString } from './utils'; // Filters for classes const classFilters: any = {}; let filteredClasses: any = {}; let level: LoggerLevel; // Save the process id const pid = process.pid; // current logger // eslint-disable-next-line no-console let currentLogger: LoggerFunction = console.warn; /** @public */ export const LoggerLevel = Object.freeze({ ERROR: 'error', WARN: 'warn', INFO: 'info', DEBUG: 'debug', error: 'error', warn: 'warn', info: 'info', debug: 'debug' } as const); /** @public */ export type LoggerLevel = typeof LoggerLevel[keyof typeof LoggerLevel]; /** @public */ export type LoggerFunction = (message?: any, ...optionalParams: any[]) => void; /** @public */ export interface LoggerOptions { logger?: LoggerFunction; loggerLevel?: LoggerLevel; } /** * @public */ export class Logger { className: string; /** * Creates a new Logger instance * * @param className - The Class name associated with the logging instance * @param options - Optional logging settings */ constructor(className: string, options?: LoggerOptions) { options = options ?? {}; // Current reference this.className = className; // Current logger if (!(options.logger instanceof Logger) && typeof options.logger === 'function') { currentLogger = options.logger; } // Set level of logging, default is error if (options.loggerLevel) { level = options.loggerLevel || LoggerLevel.ERROR; } // Add all class names if (filteredClasses[this.className] == null) { classFilters[this.className] = true; } } /** * Log a message at the debug level * * @param message - The message to log * @param object - Additional meta data to log */ debug(message: string, object?: unknown): void { if ( this.isDebug() && ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) || (Object.keys(filteredClasses).length === 0 && classFilters[this.className])) ) { const dateTime = new Date().getTime(); const msg = format('[%s-%s:%s] %s %s', 'DEBUG', this.className, pid, dateTime, message); const state = { type: LoggerLevel.DEBUG, message, className: this.className, pid, date: dateTime } as any; if (object) state.meta = object; currentLogger(msg, state); } } /** * Log a message at the warn level * * @param message - The message to log * @param object - Additional meta data to log */ warn(message: string, object?: unknown): void { if ( this.isWarn() && ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) || (Object.keys(filteredClasses).length === 0 && classFilters[this.className])) ) { const dateTime = new Date().getTime(); const msg = format('[%s-%s:%s] %s %s', 'WARN', this.className, pid, dateTime, message); const state = { type: LoggerLevel.WARN, message, className: this.className, pid, date: dateTime } as any; if (object) state.meta = object; currentLogger(msg, state); } } /** * Log a message at the info level * * @param message - The message to log * @param object - Additional meta data to log */ info(message: string, object?: unknown): void { if ( this.isInfo() && ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) || (Object.keys(filteredClasses).length === 0 && classFilters[this.className])) ) { const dateTime = new Date().getTime(); const msg = format('[%s-%s:%s] %s %s', 'INFO', this.className, pid, dateTime, message); const state = { type: LoggerLevel.INFO, message, className: this.className, pid, date: dateTime } as any; if (object) state.meta = object; currentLogger(msg, state); } } /** * Log a message at the error level * * @param message - The message to log * @param object - Additional meta data to log */ error(message: string, object?: unknown): void { if ( this.isError() && ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) || (Object.keys(filteredClasses).length === 0 && classFilters[this.className])) ) { const dateTime = new Date().getTime(); const msg = format('[%s-%s:%s] %s %s', 'ERROR', this.className, pid, dateTime, message); const state = { type: LoggerLevel.ERROR, message, className: this.className, pid, date: dateTime } as any; if (object) state.meta = object; currentLogger(msg, state); } } /** Is the logger set at info level */ isInfo(): boolean { return level === LoggerLevel.INFO || level === LoggerLevel.DEBUG; } /** Is the logger set at error level */ isError(): boolean { return level === LoggerLevel.ERROR || level === LoggerLevel.INFO || level === LoggerLevel.DEBUG; } /** Is the logger set at error level */ isWarn(): boolean { return ( level === LoggerLevel.ERROR || level === LoggerLevel.WARN || level === LoggerLevel.INFO || level === LoggerLevel.DEBUG ); } /** Is the logger set at debug level */ isDebug(): boolean { return level === LoggerLevel.DEBUG; } /** Resets the logger to default settings, error and no filtered classes */ static reset(): void { level = LoggerLevel.ERROR; filteredClasses = {}; } /** Get the current logger function */ static currentLogger(): LoggerFunction { return currentLogger; } /** * Set the current logger function * * @param logger - Custom logging function */ static setCurrentLogger(logger: LoggerFunction): void { if (typeof logger !== 'function') { throw new MongoInvalidArgumentError('Current logger must be a function'); } currentLogger = logger; } /** * Filter log messages for a particular class * * @param type - The type of filter (currently only class) * @param values - The filters to apply */ static filter(type: string, values: string[]): void { if (type === 'class' && Array.isArray(values)) { filteredClasses = {}; values.forEach(x => (filteredClasses[x] = true)); } } /** * Set the current log level * * @param newLevel - Set current log level (debug, warn, info, error) */ static setLevel(newLevel: LoggerLevel): void { if ( newLevel !== LoggerLevel.INFO && newLevel !== LoggerLevel.ERROR && newLevel !== LoggerLevel.DEBUG && newLevel !== LoggerLevel.WARN ) { throw new MongoInvalidArgumentError( `Argument "newLevel" should be one of ${enumToString(LoggerLevel)}` ); } level = newLevel; } }