logger.ts•8.53 kB
/**
 * 日志级别枚举
 */
export enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  LOG = 2,
  WARN = 3,
  ERROR = 4,
  NONE = 5
}
// 导入文件系统模块
import * as fs from 'fs';
import * as path from 'path';
/**
 * 日志管理器配置接口
 */
export interface LoggerConfig {
  enabled: boolean;     // 日志总开关
  minLevel: LogLevel;
  showTimestamp: boolean;
  showLevel: boolean;
  timestampFormat?: string;
  logToFile: boolean;
  logFilePath: string;
  maxObjectDepth: number;
  maxObjectStringLength: number;
}
/**
 * 增强的日志管理器类
 * 提供可配置的日志记录功能,支持不同日志级别和格式化
 */
export class Logger {
  private static config: LoggerConfig = {
    enabled: true,        // 默认开启日志
    minLevel: LogLevel.DEBUG,  // 修改为DEBUG级别,确保捕获所有日志
    showTimestamp: true,
    showLevel: true,
    timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
    logToFile: false,
    logFilePath: 'log/log.txt',
    maxObjectDepth: 2,         // 限制对象序列化深度
    maxObjectStringLength: 5000000 // 限制序列化后字符串长度
  };
  /**
   * 配置日志管理器
   * @param config 日志配置项
   */
  public static configure(config: Partial<LoggerConfig>): void {
    this.config = { ...this.config, ...config };
    
    // 确保日志目录存在
    if (this.config.logToFile && this.config.enabled) {
      const logDir = path.dirname(this.config.logFilePath);
      if (!fs.existsSync(logDir)) {
        fs.mkdirSync(logDir, { recursive: true });
      }
    }
  }
  /**
   * 设置日志开关
   * @param enabled 是否启用日志
   */
  public static setEnabled(enabled: boolean): void {
    this.config.enabled = enabled;
  }
  /**
   * 检查日志是否可输出
   * @param level 日志级别
   * @returns 是否可输出
   */
  private static canLog(level: LogLevel): boolean {
    return this.config.enabled && level >= this.config.minLevel;
  }
  /**
   * 格式化日志消息
   * @param level 日志级别
   * @param args 日志参数
   * @returns 格式化后的日志字符串数组
   */
  private static formatLogMessage(level: LogLevel, args: any[]): any[] {
    const result: any[] = [];
    
    // 添加时间戳
    if (this.config.showTimestamp) {
      const now = new Date();
      const timestamp = this.formatDate(now, this.config.timestampFormat || 'yyyy-MM-dd HH:mm:ss.SSS');
      result.push(`[${timestamp}]`);
    }
    
    // 添加日志级别
    if (this.config.showLevel) {
      const levelStr = LogLevel[level].padEnd(5, ' ');
      result.push(`[${levelStr}]`);
    }
    
    // 添加原始日志内容
    return [...result, ...args];
  }
  /**
   * 将日志写入文件
   * @param logParts 日志内容部分
   */
  private static writeToFile(logParts: any[]): void {
    if (!this.config.enabled || !this.config.logToFile) return;
    
    try {
      // 将日志内容转换为字符串
      let logString = '';
      for (const part of logParts) {
        if (typeof part === 'object') {
          try {
            // 简化对象序列化
            logString += this.safeStringify(part) + ' ';
          } catch (e) {
            logString += '[Object] ';
          }
        } else {
          logString += part + ' ';
        }
      }
      
      // 添加换行符
      logString += '\n';
      
      // 以追加模式写入文件
      fs.appendFileSync(this.config.logFilePath, logString);
    } catch (error) {
      console.error('写入日志文件失败:', error);
    }
  }
  /**
   * 安全的对象序列化,限制深度和长度
   * @param obj 要序列化的对象
   * @returns 序列化后的字符串
   */
  private static safeStringify(obj: any): string {
    const seen = new Set();
    
    const stringified = JSON.stringify(obj, (key, value) => {
      // 处理循环引用
      if (typeof value === 'object' && value !== null) {
        if (seen.has(value)) {
          return '[Circular]';
        }
        seen.add(value);
      }
      
      // 处理请求/响应对象
      if (key === 'request' || key === 'socket' || key === 'agent' || 
          key === '_events' || key === '_eventsCount' || key === '_maxListeners' ||
          key === 'rawHeaders' || key === 'rawTrailers') {
        return '[Object]';
      }
      
      return value;
    }, 2);
    
    if (stringified && stringified.length > this.config.maxObjectStringLength) {
      return stringified.substring(0, this.config.maxObjectStringLength) + '... [截断]';
    }
    
    return stringified;
  }
  /**
   * 格式化日期
   * @param date 日期对象
   * @param format 格式字符串
   * @returns 格式化后的日期字符串
   */
  private static formatDate(date: Date, format: string): string {
    const year = date.getFullYear().toString();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    const hours = date.getHours().toString().padStart(2, '0');
    const minutes = date.getMinutes().toString().padStart(2, '0');
    const seconds = date.getSeconds().toString().padStart(2, '0');
    const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
    return format
      .replace('yyyy', year)
      .replace('MM', month)
      .replace('dd', day)
      .replace('HH', hours)
      .replace('mm', minutes)
      .replace('ss', seconds)
      .replace('SSS', milliseconds);
  }
  /**
   * 记录调试级别日志
   * @param args 日志参数
   */
  public static debug(...args: any[]): void {
    if (this.canLog(LogLevel.DEBUG)) {
      const formattedMessage = this.formatLogMessage(LogLevel.DEBUG, args);
      console.debug(...formattedMessage);
      this.writeToFile(formattedMessage);
    }
  }
  /**
   * 记录信息级别日志
   * @param args 日志参数
   */
  public static info(...args: any[]): void {
    if (this.canLog(LogLevel.INFO)) {
      const formattedMessage = this.formatLogMessage(LogLevel.INFO, args);
      console.info(...formattedMessage);
      this.writeToFile(formattedMessage);
    }
  }
  /**
   * 记录普通级别日志
   * @param args 日志参数
   */
  public static log(...args: any[]): void {
    if (this.canLog(LogLevel.LOG)) {
      const formattedMessage = this.formatLogMessage(LogLevel.LOG, args);
      console.log(...formattedMessage);
      this.writeToFile(formattedMessage);
    }
  }
  /**
   * 记录警告级别日志
   * @param args 日志参数
   */
  public static warn(...args: any[]): void {
    if (this.canLog(LogLevel.WARN)) {
      const formattedMessage = this.formatLogMessage(LogLevel.WARN, args);
      console.warn(...formattedMessage);
      this.writeToFile(formattedMessage);
    }
  }
  /**
   * 记录错误级别日志
   * @param args 日志参数
   */
  public static error(...args: any[]): void {
    if (this.canLog(LogLevel.ERROR)) {
      const formattedMessage = this.formatLogMessage(LogLevel.ERROR, args);
      console.error(...formattedMessage);
      this.writeToFile(formattedMessage);
    }
  }
  /**
   * 记录请求和响应的详细信息
   * @param method 请求方法
   * @param url 请求URL
   * @param data 请求数据
   * @param response 响应数据
   * @param statusCode 响应状态码
   */
  public static logApiCall(method: string, url: string, data: any, response: any, statusCode: number): void {
    if (this.canLog(LogLevel.DEBUG)) {
      this.debug('API调用详情:');
      this.debug(`请求: ${method} ${url}`);
      
      // 简化请求数据记录
      if (data) {
        try {
          if (typeof data === 'string') {
            // 尝试解析JSON字符串
            const parsedData = JSON.parse(data);
            this.debug('请求数据:', parsedData);
          } else {
            this.debug('请求数据:', data);
          }
        } catch (e) {
          this.debug('请求数据:', data);
        }
      } else {
        this.debug('请求数据: None');
      }
      
      this.debug(`响应状态: ${statusCode}`);
      
      // 简化响应数据记录
      if (response) {
        // 只记录关键信息
        const simplifiedResponse = response.data ? { data: response.data } : response;
        this.debug('响应数据:', simplifiedResponse);
      } else {
        this.debug('响应数据: None');
      }
    } else if (this.canLog(LogLevel.INFO)) {
      this.info(`API调用: ${method} ${url} - 状态码: ${statusCode}`);
    }
  }
}