大体思路 通过 winston 生成日志实例,可实现代码中关键位置输出日志,和错误日志的统一管理,通过winston-daily-rotate-file实现 日志分级储存。
import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
// 1. 定义日志级别类型
type LogLevel = 'error' | 'warn' | 'info' | 'debug';
// 敏感字段定义
const SENSITIVE_KEYS = ['password', 'ssn', 'cvv'];
// 深度过滤函数
const sanitize = (data: any) => {
if (typeof data !== 'object') return data;
return Object.entries(data).reduce((acc: any, [key, value]) => {
//嵌套类处理
const tempValue = typeof value === 'object' ? sanitize(value)
: value;
acc[key] = SENSITIVE_KEYS.includes(key) ? '[FILTERED]' : tempValue;
return acc;
}, {});
};
const errorFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }), // 强制输出错误堆栈
winston.format.printf(({ timestamp, level, message, stack }) => {
return `${timestamp} [${level.toUpperCase()}] ${message}${stack ? '\n' + stack : ''}`;
})
);
const infoFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(), // 必须启用splat格式化
winston.format.printf(info => {
if(info.level == 'info') {
info.message = typeof info.message === 'object'
? sanitize(info.message)
: info.message;
const args = (info[Symbol.for('splat')] as Array)?.map(arg =>
arg instanceof Object ? sanitize(arg) : arg
) || [];
return `${info.timestamp} [${info.level.toUpperCase()}]: ${JSON.stringify(info.message)} ${args.length > 0 ? ': '+JSON.stringify(args) : ''}`;
} else if(info.level == 'warn') {
return `${info.timestamp} [${info.level.toUpperCase()}]: ${JSON.stringify(info.message)}`;
}
return `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}${info.stack ? '\n' + info.stack : ''}`;
})
);
// 2. 配置日志传输渠道
const errorTransport = new DailyRotateFile({
filename: 'logs/error-%DATE%.log',
level: 'error',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d',
format: errorFormat
});
const combinedTransport = new DailyRotateFile({
filename: 'logs/combined-%DATE%.log',
level: 'info',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d',
format: infoFormat
});
// 3. 创建Logger实例
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
errorTransport,
combinedTransport
]
});
// 4. 导出类型化接口
export interface Logger {
error: (message: string, meta?: Record) => void;
warn: (message: string, meta?: Record) => void;
info: (message: string, meta?: Record) => void;
debug: (message: string, meta?: Record) => void;
}
// 5. 导出实例
export default logger as Logger;
sanitize 方法是对敏感隐私字段过滤函数。
errorFormat 是错误日志专用的格式化(最终体现在error-%DATE%.log中)
infoFormat 是info以上级别的日志统一用的格式化(最终体现在combined-%DATE%.log中)
最后通过winston.createLogger 创建日志实例。
maxSize: '20m': 最大一个日志文件支持20M大小,到限制会重新生成一个例如 error-2025-07-04.1.log 的新文件。
maxFiles: '30d':设置最大保留时间为30 天。
注意:要获取错误信息堆载信息 需要 winston.format.errors({ stack: true }),配置。
logger.error(message, {
message: error.message,
method: req.method,
stack:error.stack,
route: req.originalUrl
});
logger.info(message, other)
logger.warn(message, other)
以上就是一个简单的关于Express 生成文件日志的例子。
后续可优化部分:开发环境和生产环境日志定制化(例如:生产环境日志使用winston.format.json配置,不用 winston.format.printf自定义格式,可压缩日志文件大小)