import * as winston from 'winston';
import { loggerConfig } from './config/logger';
const developmentFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
const metaStr = Object.keys(meta).length ? JSON.stringify(meta, null, 2) : '';
return `${timestamp} [${level}]: ${message} ${metaStr}`;
}),
);
const maskFormat = winston.format((info) => {
return maskSensitiveData(info);
})();
const productionFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
maskFormat,
winston.format.json(),
);
function maskSensitiveData(obj: any): any {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
const masked = { ...obj };
for (const [key, value] of Object.entries(masked)) {
const lowerKey = key.toLowerCase();
const isSensitive = loggerConfig.sensitive.maskPatterns.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
if (isSensitive && typeof value === 'string') {
masked[key] = '***MASKED***';
} else if (typeof value === 'object' && value !== null) {
masked[key] = maskSensitiveData(value);
}
}
return masked;
}
const transports: winston.transport[] = [];
if (loggerConfig.console.enabled) {
transports.push(
new winston.transports.Console({
format: process.env.NODE_ENV === 'development' ? developmentFormat : productionFormat,
}),
);
}
if (loggerConfig.file.enabled) {
transports.push(
new winston.transports.File({
filename: loggerConfig.file.path,
maxsize: parseSize(loggerConfig.file.maxSize),
maxFiles: loggerConfig.file.maxFiles,
format: productionFormat,
}),
);
}
function parseSize(sizeStr: string): number {
const units: { [key: string]: number } = {
b: 1,
k: 1024,
m: 1024 * 1024,
g: 1024 * 1024 * 1024,
};
const match = sizeStr.toLowerCase().match(/^(\d+)([kmg]?)b?$/);
if (!match) return 10 * 1024 * 1024;
const size = parseInt(match[1] || '0', 10);
const unit = match[2] || 'b';
return size * (units[unit] || 1);
}
export const logger = winston.createLogger({
level: loggerConfig.level,
transports,
exitOnError: false,
exceptionHandlers: [
new winston.transports.Console(),
...(loggerConfig.file.enabled ? [new winston.transports.File({ filename: 'logs/exceptions.log' })] : []),
],
rejectionHandlers: [
new winston.transports.Console(),
...(loggerConfig.file.enabled ? [new winston.transports.File({ filename: 'logs/rejections.log' })] : []),
],
});
export const httpLogStream = {
write: (message: string) => {
logger.info(message.trim());
},
};
export default logger;