Photo by Jon Flobrant on Unsplash
This is part two and example of the logging best practices. For part 1: Logging Best Practices with Example
Features
- Logging at multiple different levels.
- Discarding debug messages in production.
- Writing log messages also file in production.
- Maximum file size of log files.
- The daily rotation of log files.
- Preserving log files for a period of time
- Compressing log files
- Structured logging
- Including context in a log message
Cons
- Function, file name, and line number not be shown in the log message.
import {injectable} from 'inversify'; // For dependency injection
import winston, {format} from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
export type LogMessage = string;
export type LogContext = object;
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
}
@injectable() // Coming from inversify
export class Logging {
private _logger: winston.Logger;
private static _appName = 'ApplicationName';
constructor() {
this._logger = this._initializeWinston();
}
public logInfo(msg: LogMessage, context?: LogContext) {
this._log(msg, LogLevel.INFO, context);
}
public logWarn(msg: LogMessage, context?: LogContext) {
this._log(msg, LogLevel.WARN, context);
}
public logError(msg: LogMessage, context?: LogContext) {
this._log(msg, LogLevel.ERROR, context);
}
public logDebug(msg: LogMessage, context?: LogContext) {
if (process.env.NODE_ENV !== 'production') {
this._log(msg, LogLevel.DEBUG, context); // Don't log debug in production
}
}
private _log(msg: LogMessage, level: LogLevel, context?: LogContext) {
this._logger.log(level, msg, {context: context});
}
private _initializeWinston() {
const logger = winston.createLogger({
transports: Logging._getTransports(),
});
return logger;
}
private static _getTransports() {
const transports: Array<any> = [
new winston.transports.Console({
format: this._getFormatForConsole(),
}),
];
if (process.env.NODE_ENV === 'production') {
transports.push(this._getFileTransport()); // Also log file in production
}
return transports;
}
private static _getFormatForConsole() {
return format.combine(
format.timestamp(),
format.printf(
info =>
`[${info.timestamp}] [${info.level.toUpperCase()}]: ${
info.message
} [CONTEXT] -> ${
info.context ? '\n' + JSON.stringify(info.context, null, 2) : '{}' // Including the context
}`
),
format.colorize({all: true})
);
}
private static _getFileTransport() {
return new DailyRotateFile({
filename: `${Logging._appName}-%DATE%.log`,
zippedArchive: true, // Compress gzip
maxSize: '10m', // Rotate after 10MB
maxFiles: '14d', // Only keep last 14 days
format: format.combine(
format.timestamp(),
format(info => {
console.log(info);
info.app = this._appName;
return info;
})(),
format.json()
),
});
}
}