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;