# All files / services logger.service.ts

0% Statements 0/90
0% Branches 0/1
0% Functions 0/1
0% Lines 0/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114

     import winston from 'winston';
    import path from 'path';
    import fs from 'fs';

    // 确保日志目录存在
    const logDir = path.join(process.cwd(), 'logs');
    if (!fs.existsSync(logDir)) {
      fs.mkdirSync(logDir, { recursive: true });
    }

    // 日志格式
    const logFormat = winston.format.printf(({ level, message, timestamp, stack, ...meta }) => {
      const metaStr = Object.keys(meta).length > 0 ? `\n${JSON.stringify(meta, null, 2)}` : '';
      return `${timestamp} [${level}]: ${stack || message}${metaStr}`;
    });

    // 创建日志传输配置
    const transports: winston.transport[] = [
      // 控制台输出
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.colorize(),
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
          winston.format.simple()
        ),
        level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
      }),

      // 错误日志文件
      new winston.transports.File({
        filename: path.join(logDir, 'error.log'),
        level: 'error',
        format: winston.format.combine(
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
          winston.format.errors({ stack: true }),
          logFormat
        ),
        maxsize: 10 * 1024 * 1024, // 10MB
        maxFiles: 10,
      }),

      // 常规日志文件
      new winston.transports.File({
        filename: path.join(logDir, 'combined.log'),
        format: winston.format.combine(
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
          winston.format.errors({ stack: true }),
          logFormat
        ),
        maxsize: 10 * 1024 * 1024, // 10MB
        maxFiles: 10,
      }),

      // HTTP 请求日志
      new winston.transports.File({
        filename: path.join(logDir, 'http.log'),
        level: 'http',
        format: winston.format.combine(
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
          logFormat
        ),
        maxsize: 10 * 1024 * 1024,
        maxFiles: 5,
      }),
    ];

    // 创建日志记录器
    const logger = winston.createLogger({
      level: process.env.LOG_LEVEL || 'debug',
      transports,
      exitOnError: false,
    });

    // HTTP 请求日志中间件
    export function httpLogger(ctx: any, next: any) {
      const start = Date.now();

      return next().then(() => {
        const ms = Date.now() - start;
        logger.http({
          type: 'request',
          method: ctx.method,
          url: ctx.url,
          status: ctx.status,
          duration: `${ms}ms`,
          ip: ctx.ip,
          userAgent: ctx.headers['user-agent'],
        });
      }).catch((err: any) => {
        const ms = Date.now() - start;
        logger.error({
          type: 'request_error',
          method: ctx.method,
          url: ctx.url,
          status: err.status || 500,
          duration: `${ms}ms`,
          error: err.message,
          stack: err.stack,
        });
        throw err;
      });
    }

    // 便捷方法
    export const log = {
      info: (message: string, meta?: any) => logger.info(message, meta),
      warn: (message: string, meta?: any) => logger.warn(message, meta),
      error: (message: string, meta?: any) => logger.error(message, meta),
      debug: (message: string, meta?: any) => logger.debug(message, meta),
      http: (message: string, meta?: any) => logger.http(message, meta),
    };

    export default logger;