Last active
January 3, 2024 22:07
-
-
Save RyadPasha/4d2d86c66e1c33be3252933dac0b1116 to your computer and use it in GitHub Desktop.
Logger class that wraps 'winston' and provides custom logging capabilities with different log levels.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Logger class that wraps 'winston' and provides custom | |
* logging capabilities with different log levels. | |
* | |
* @class Logger | |
* @author Mohamed Riyad <m@ryad.dev> | |
* @link https://RyadPasha.com | |
* @copyright Copyright (C) 2024 RyadPasha. All rights reserved. | |
* @license MIT | |
* @version 1.0.2-2024.01.03 | |
* @see {@link https://github.com/winstonjs/winston} for more information on Winston | |
* @see {@link https://gist.github.com/RyadPasha/4d2d86c66e1c33be3252933dac0b1116} for updates | |
*/ | |
import CircularJSON from 'circular-json' | |
import winston, { format, transports } from 'winston' | |
import DailyRotateFile from 'winston-daily-rotate-file' | |
class Logger { | |
private static instance: Logger | |
private logger: winston.Logger | |
private maxFileSize: number = 5242880 // 5MB | |
private maxFiles: string = '14d' // Keep logs for 14 days | |
private validLevels: string[] = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'] | |
private defaultLevel: string = 'info' | |
private logLevel: string = process.env.LOG_LEVEL || 'silly' // Default log level | |
private consoleLogLevel: string = process.env.CONSOLE_LOG_LEVEL || this.logLevel // Default console log level | |
private fileLogLevel: string = process.env.FILE_LOG_LEVEL || this.logLevel // Default file log level | |
/** | |
* Private constructor for the Logger class. | |
* Configures the Winston logger with console and file transports. | |
*/ | |
private constructor() { | |
// Log format for console with colorization | |
const consoleLogFormat = format.combine( | |
format.colorize({ all: true }), | |
format.timestamp({ | |
format: 'YYYY-MM-DD hh:mm:ss A' | |
}), | |
format.printf(({ timestamp, level, message }) => `[${timestamp}]: ${level}: ${message}`) | |
) | |
// Log format for files without colorization | |
const fileLogFormat = format.combine( | |
format.timestamp({ | |
format: 'YYYY-MM-DD hh:mm:ss A' | |
}), | |
format.printf(({ timestamp, level, message }) => `[${timestamp}]: ${level}: ${message}`) | |
) | |
/** | |
* Create and configure a Winston logger with file rotation. | |
* | |
* @type {Logger} | |
*/ | |
this.logger = winston.createLogger({ | |
level: this.logLevel, | |
format: format.combine( | |
format.timestamp({ | |
format: 'YYYY-MM-DD hh:mm:ss A' | |
}), | |
format.printf(({ timestamp, level, message }) => `[${timestamp}]: ${level}: ${message}`) | |
), | |
transports: [ | |
new transports.Console({ | |
level: this.consoleLogLevel, | |
format: consoleLogFormat | |
}), | |
new DailyRotateFile({ | |
filename: 'logs/app-%DATE%.log', | |
datePattern: 'YYYY-MM-DD', | |
level: this.fileLogLevel, | |
format: fileLogFormat, | |
maxFiles: this.maxFiles, | |
maxSize: this.maxFileSize | |
}), | |
new DailyRotateFile({ | |
filename: 'logs/error-%DATE%.log', | |
datePattern: 'YYYY-MM-DD', | |
level: 'error', | |
format: fileLogFormat, | |
maxFiles: this.maxFiles, | |
maxSize: this.maxFileSize | |
}) | |
] | |
}) | |
} | |
/** | |
* Get the instance of the Logger class (Singleton pattern). | |
* If an instance doesn't exist, it creates one; otherwise, returns the existing instance. | |
* | |
* @returns {Logger} The Logger instance. | |
*/ | |
public static getInstance(): Logger { | |
if (!Logger.instance) { | |
Logger.instance = new Logger() | |
} | |
return Logger.instance | |
} | |
/** | |
* Stringify an argument, handling objects (including Errors) and primitives. | |
* | |
* @private | |
* @param {any} arg - The argument to be stringified. | |
* @returns {string} The stringified representation of the argument. | |
*/ | |
private stringifyArg(arg: any): string { | |
if (typeof arg === 'string') { | |
return arg | |
} else if (typeof arg === 'object' && arg !== null) { | |
if (arg instanceof Error) { | |
// If the argument is an Error object and not the only argument, stringify it | |
const errorData: Record<string, any> = {} | |
// Include the non-enumerable properties separately | |
// 1. the error message | |
if (arg.message) errorData.message = arg.message | |
// 2. the stack trace | |
if (arg.stack) errorData.stack = arg.stack | |
// Handle custom fields | |
for (const prop in arg) { | |
if (Object.prototype.hasOwnProperty.call(arg, prop) && prop !== 'message' && prop !== 'stack') { | |
errorData[prop] = arg[prop] | |
} | |
} | |
return CircularJSON.stringify(errorData, null, 2) | |
} else { | |
return CircularJSON.stringify(arg, null, 2) | |
} | |
} else { | |
return String(arg) | |
} | |
} | |
/** | |
* Public method to log messages with different log levels. | |
* | |
* @param {string} level - The log level ('info', 'warning', 'error', etc.). | |
* @param {...any[]} args - Arguments to be logged, which will be stringified. | |
*/ | |
public log(level: string, ...args: any[]) { | |
const selectedLevel = this.validLevels.includes(level) ? level : this.defaultLevel | |
const message = args.map((arg) => this.stringifyArg(arg)).join(' ') | |
this.logger.log(selectedLevel, message) | |
} | |
/** | |
* Log an informational message. | |
* | |
* @param {...any[]} args - Arguments to be logged. | |
*/ | |
public info(...args: any[]) { | |
this.log('info', ...args) | |
} | |
/** | |
* Log a warning message. | |
* | |
* @param {...any[]} args - Arguments to be logged. | |
*/ | |
public warn(...args: any[]) { | |
this.log('warn', ...args) | |
} | |
/** | |
* Log an error message. | |
* | |
* @param {...any[]} args - Arguments to be logged. | |
*/ | |
public error(...args: any[]) { | |
this.log('error', ...args) | |
} | |
/** | |
* Log a debug message. | |
* | |
* @param {...any[]} args - Arguments to be logged. | |
*/ | |
public debug(...args: any[]) { | |
this.log('debug', ...args) | |
} | |
/** | |
* Log a silly message. | |
*/ | |
public silly(...args: any[]) { | |
this.log('silly', ...args) | |
} | |
/** | |
* Log a verbose message. | |
*/ | |
public verbose(...args: any[]) { | |
this.log('verbose', ...args) | |
} | |
} | |
export default Logger.getInstance() // Export a single instance of the Logger |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment