日志系统的设计与实现

在现代Web应用开发中,日志系统是JavaScript错误处理与调试不可或缺的组成部分。一个设计良好的日志系统不仅能帮助开发者快速定位问题,还能提供应用运行时的宝贵洞察。本文将深入探讨如何在JavaScript应用中设计和实现一个高效的日志系统。

日志系统的基本架构

1. 日志级别设计

一个完善的日志系统应当包含多个日志级别:

javascript 复制代码
const LogLevel = {
  DEBUG: 'debug',
  INFO: 'info',
  WARN: 'warn',
  ERROR: 'error',
  FATAL: 'fatal'
};

2. 日志记录器核心

javascript 复制代码
class Logger {
  constructor(options = {}) {
    this.level = options.level || LogLevel.INFO;
    this.transports = options.transports || [new ConsoleTransport()];
  }

  log(level, message, meta = {}) {
    if (this.shouldLog(level)) {
      const logEntry = {
        timestamp: new Date().toISOString(),
        level,
        message,
        ...meta
      };
      
      this.transports.forEach(transport => transport.log(logEntry));
    }
  }

  shouldLog(level) {
    const levels = Object.values(LogLevel);
    return levels.indexOf(level) >= levels.indexOf(this.level);
  }

  // 快捷方法
  debug(message, meta) { this.log(LogLevel.DEBUG, message, meta); }
  info(message, meta) { this.log(LogLevel.INFO, message, meta); }
  warn(message, meta) { this.log(LogLevel.WARN, message, meta); }
  error(message, meta) { this.log(LogLevel.ERROR, message, meta); }
  fatal(message, meta) { this.log(LogLevel.FATAL, message, meta); }
}

日志传输(Transport)实现

1. 控制台输出

javascript 复制代码
class ConsoleTransport {
  log(entry) {
    const method = console[entry.level] || console.log;
    method(`[${entry.timestamp}] [${entry.level.toUpperCase()}] ${entry.message}`);
    if (Object.keys(entry).length > 4) { // 有额外元数据
      console.dir(entry);
    }
  }
}

2. 远程服务器传输

javascript 复制代码
class HttpTransport {
  constructor({ url, batchSize = 5, timeout = 5000 }) {
    this.url = url;
    this.batchSize = batchSize;
    this.timeout = timeout;
    this.queue = [];
    this.timer = null;
  }

  log(entry) {
    this.queue.push(entry);
    
    if (this.queue.length >= this.batchSize) {
      this.flush();
    } else if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.timeout);
    }
  }

  flush() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
    
    if (this.queue.length === 0) return;
    
    const batch = [...this.queue];
    this.queue = [];
    
    fetch(this.url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(batch)
    }).catch(error => {
      console.error('Failed to send logs:', error);
      // 可以选择重试或降级处理
    });
  }
}

错误上下文增强

1. 错误堆栈增强

javascript 复制代码
function enhanceError(error) {
  if (!error.stack) {
    const stack = new Error().stack.split('\n');
    stack.shift(); // 移除当前行
    error.stack = stack.join('\n');
  }
  return error;
}

2. 全局错误捕获

javascript 复制代码
window.addEventListener('error', (event) => {
  logger.error('Uncaught error', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    error: enhanceError(event.error)
  });
});

window.addEventListener('unhandledrejection', (event) => {
  logger.error('Unhandled promise rejection', {
    reason: enhanceError(event.reason)
  });
});

性能优化与最佳实践

1. 生产环境日志优化

javascript 复制代码
// 生产环境只记录WARN及以上级别
const logger = new Logger({
  level: process.env.NODE_ENV === 'production' ? LogLevel.WARN : LogLevel.DEBUG,
  transports: [
    new ConsoleTransport(),
    new HttpTransport({ url: '/api/logs' })
  ]
});

2. 敏感信息过滤

javascript 复制代码
class SanitizingTransport {
  constructor(transport) {
    this.transport = transport;
  }
  
  log(entry) {
    const sanitized = { ...entry };
    // 过滤敏感信息
    if (sanitized.meta && sanitized.meta.headers) {
      sanitized.meta.headers.authorization = '[REDACTED]';
    }
    this.transport.log(sanitized);
  }
}

3. 采样率控制

javascript 复制代码
class SamplingTransport {
  constructor(transport, sampleRate = 1) {
    this.transport = transport;
    this.sampleRate = sampleRate;
  }
  
  log(entry) {
    if (Math.random() < this.sampleRate) {
      this.transport.log(entry);
    }
  }
}

日志分析与可视化

1. 结构化日志格式

javascript 复制代码
{
  "timestamp": "2023-05-15T12:34:56.789Z",
  "level": "error",
  "message": "API request failed",
  "context": {
    "userId": "12345",
    "requestId": "abcde-12345",
    "url": "/api/data",
    "status": 500
  },
  "error": {
    "message": "Connection timeout",
    "stack": "..."
  }
}

2. 前端日志聚合

考虑使用Sentry、LogRocket等专业服务,或自建ELK(Elasticsearch, Logstash, Kibana)栈。

结论

一个精心设计的JavaScript日志系统能够显著提升应用的可靠性和可维护性。通过实现多级日志记录、多种传输方式、错误上下文增强和性能优化,开发者可以构建出既满足开发调试需求,又适应生产环境的强大日志系统。记住,好的日志系统不仅记录问题,还能帮助预防问题,是JavaScript应用健康运行的守护者。