Express + winston + winston-daily-rotate-file 实现日志管理

大体思路 通过 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自定义格式,可压缩日志文件大小)

你可能感兴趣的:(Node.js,express)