Files
jiebanke/docs/错误处理和日志系统文档.md

20 KiB

错误处理和日志系统文档

概述

错误处理和日志系统是解班客平台的核心基础设施,提供统一的错误处理机制、完善的日志记录功能和系统监控能力。系统采用分层设计,支持多种错误类型处理、多级日志记录和实时监控。

系统架构

核心组件

  1. 错误处理中间件 (middleware/errorHandler.js)

    • 全局错误捕获
    • 错误分类处理
    • 统一错误响应
    • 错误日志记录
  2. 日志记录系统 (utils/logger.js)

    • 多级日志记录
    • 日志格式化
    • 日志轮转管理
    • 性能监控
  3. 自定义错误类

    • 业务错误定义
    • 错误码管理
    • 错误信息国际化
    • 错误堆栈追踪

错误处理机制

错误分类

1. 业务错误 (Business Errors)

  • 用户认证错误: 登录失败、token过期等
  • 权限错误: 无权限访问、操作被拒绝等
  • 数据验证错误: 参数格式错误、必填项缺失等
  • 业务逻辑错误: 余额不足、状态不允许等

2. 系统错误 (System Errors)

  • 数据库错误: 连接失败、查询超时等
  • 网络错误: 请求超时、连接中断等
  • 文件系统错误: 文件不存在、权限不足等
  • 第三方服务错误: API调用失败、服务不可用等

3. 程序错误 (Programming Errors)

  • 语法错误: 代码语法问题
  • 运行时错误: 空指针、类型错误等
  • 内存错误: 内存溢出、内存泄漏等
  • 配置错误: 配置文件错误、环境变量缺失等

错误处理流程

graph TD
    A[请求开始] --> B[业务逻辑处理]
    B --> C{是否发生错误?}
    C -->|否| D[正常响应]
    C -->|是| E[错误捕获]
    E --> F[错误分类]
    F --> G[错误日志记录]
    G --> H[错误响应格式化]
    H --> I[返回错误响应]
    D --> J[请求结束]
    I --> J

自定义错误类

AppError 类

class AppError extends Error {
  constructor(message, statusCode, errorCode = null, isOperational = true) {
    super(message);
    
    this.statusCode = statusCode;
    this.errorCode = errorCode;
    this.isOperational = isOperational;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    
    Error.captureStackTrace(this, this.constructor);
  }
}

错误类型定义

const ErrorTypes = {
  // 认证相关错误
  AUTH_TOKEN_MISSING: { code: 'AUTH_001', message: '缺少认证令牌' },
  AUTH_TOKEN_INVALID: { code: 'AUTH_002', message: '无效的认证令牌' },
  AUTH_TOKEN_EXPIRED: { code: 'AUTH_003', message: '认证令牌已过期' },
  
  // 权限相关错误
  PERMISSION_DENIED: { code: 'PERM_001', message: '权限不足' },
  RESOURCE_FORBIDDEN: { code: 'PERM_002', message: '资源访问被禁止' },
  
  // 验证相关错误
  VALIDATION_FAILED: { code: 'VALID_001', message: '数据验证失败' },
  REQUIRED_FIELD_MISSING: { code: 'VALID_002', message: '必填字段缺失' },
  INVALID_FORMAT: { code: 'VALID_003', message: '数据格式无效' },
  
  // 业务逻辑错误
  RESOURCE_NOT_FOUND: { code: 'BIZ_001', message: '资源不存在' },
  RESOURCE_ALREADY_EXISTS: { code: 'BIZ_002', message: '资源已存在' },
  OPERATION_NOT_ALLOWED: { code: 'BIZ_003', message: '操作不被允许' },
  
  // 系统错误
  DATABASE_ERROR: { code: 'SYS_001', message: '数据库操作失败' },
  FILE_SYSTEM_ERROR: { code: 'SYS_002', message: '文件系统错误' },
  NETWORK_ERROR: { code: 'SYS_003', message: '网络连接错误' },
  
  // 第三方服务错误
  THIRD_PARTY_SERVICE_ERROR: { code: 'EXT_001', message: '第三方服务错误' },
  API_RATE_LIMIT_EXCEEDED: { code: 'EXT_002', message: 'API调用频率超限' }
};

错误响应格式

标准错误响应

{
  "success": false,
  "error": {
    "code": "AUTH_002",
    "message": "无效的认证令牌",
    "details": "Token signature verification failed",
    "timestamp": "2024-01-15T10:30:00.000Z",
    "path": "/api/v1/admin/users",
    "method": "GET",
    "requestId": "req_1234567890"
  }
}

验证错误响应

{
  "success": false,
  "error": {
    "code": "VALID_001",
    "message": "数据验证失败",
    "details": {
      "email": ["邮箱格式不正确"],
      "password": ["密码长度至少8位", "密码必须包含数字和字母"]
    },
    "timestamp": "2024-01-15T10:30:00.000Z",
    "path": "/api/v1/auth/register",
    "method": "POST",
    "requestId": "req_1234567891"
  }
}

日志系统

日志级别

1. ERROR (错误)

  • 用途: 记录系统错误和异常
  • 示例: 数据库连接失败、未捕获的异常
  • 处理: 需要立即关注和处理

2. WARN (警告)

  • 用途: 记录潜在问题和警告信息
  • 示例: 性能警告、配置问题
  • 处理: 需要关注,但不影响系统运行

3. INFO (信息)

  • 用途: 记录重要的业务操作和系统状态
  • 示例: 用户登录、重要配置变更
  • 处理: 用于审计和监控

4. HTTP (HTTP请求)

  • 用途: 记录HTTP请求和响应信息
  • 示例: API调用、响应时间
  • 处理: 用于性能分析和调试

5. DEBUG (调试)

  • 用途: 记录详细的调试信息
  • 示例: 变量值、执行流程
  • 处理: 仅在开发环境使用

日志格式

标准日志格式

[2024-01-15 10:30:00.123] [INFO] [USER_AUTH] 用户登录成功 - userId: 12345, ip: 192.168.1.100, userAgent: Mozilla/5.0...

JSON格式日志

{
  "timestamp": "2024-01-15T10:30:00.123Z",
  "level": "INFO",
  "category": "USER_AUTH",
  "message": "用户登录成功",
  "metadata": {
    "userId": 12345,
    "ip": "192.168.1.100",
    "userAgent": "Mozilla/5.0...",
    "requestId": "req_1234567890",
    "duration": 150
  }
}

日志分类

1. 请求日志 (Request Logs)

// 记录HTTP请求信息
logger.http('API请求', {
  method: 'POST',
  url: '/api/v1/users',
  ip: '192.168.1.100',
  userAgent: 'Mozilla/5.0...',
  requestId: 'req_1234567890',
  userId: 12345,
  duration: 150,
  statusCode: 200
});

2. 业务日志 (Business Logs)

// 记录业务操作
logger.business('用户注册', {
  action: 'USER_REGISTER',
  userId: 12345,
  email: 'user@example.com',
  ip: '192.168.1.100',
  success: true
});

3. 安全日志 (Security Logs)

// 记录安全事件
logger.security('登录失败', {
  event: 'LOGIN_FAILED',
  email: 'user@example.com',
  ip: '192.168.1.100',
  reason: 'INVALID_PASSWORD',
  attempts: 3
});

4. 性能日志 (Performance Logs)

// 记录性能数据
logger.performance('数据库查询', {
  operation: 'SELECT',
  table: 'users',
  duration: 50,
  rowCount: 100,
  query: 'SELECT * FROM users WHERE status = ?'
});

5. 系统日志 (System Logs)

// 记录系统事件
logger.system('服务启动', {
  event: 'SERVER_START',
  port: 3000,
  environment: 'production',
  version: '1.0.0'
});

日志存储和轮转

日志文件结构

logs/
├── app.log                 # 应用主日志
├── error.log              # 错误日志
├── access.log             # 访问日志
├── security.log           # 安全日志
├── performance.log        # 性能日志
├── business.log           # 业务日志
└── archived/              # 归档日志
    ├── app-2024-01-14.log
    ├── error-2024-01-14.log
    └── ...

日志轮转配置

const winston = require('winston');
require('winston-daily-rotate-file');

const transport = new winston.transports.DailyRotateFile({
  filename: 'logs/app-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  zippedArchive: true,
  maxSize: '20m',
  maxFiles: '30d'
});

监控和告警

错误监控

1. 错误率监控

  • 指标: 每分钟错误数量、错误率
  • 阈值: 错误率超过5%触发告警
  • 处理: 自动发送告警通知

2. 响应时间监控

  • 指标: 平均响应时间、95%分位数
  • 阈值: 响应时间超过2秒触发告警
  • 处理: 性能优化建议

3. 系统资源监控

  • 指标: CPU使用率、内存使用率、磁盘空间
  • 阈值: 资源使用率超过80%触发告警
  • 处理: 资源扩容建议

日志分析

1. 实时日志分析

// 实时错误统计
const errorStats = {
  total: 0,
  byType: {},
  byEndpoint: {},
  recentErrors: []
};

// 更新错误统计
function updateErrorStats(error, req) {
  errorStats.total++;
  errorStats.byType[error.code] = (errorStats.byType[error.code] || 0) + 1;
  errorStats.byEndpoint[req.path] = (errorStats.byEndpoint[req.path] || 0) + 1;
  
  errorStats.recentErrors.unshift({
    timestamp: new Date(),
    code: error.code,
    message: error.message,
    path: req.path,
    method: req.method
  });
  
  // 保持最近100个错误
  if (errorStats.recentErrors.length > 100) {
    errorStats.recentErrors.pop();
  }
}

2. 日志聚合分析

// 按时间段聚合日志
function aggregateLogs(startTime, endTime) {
  return {
    totalRequests: 0,
    successRequests: 0,
    errorRequests: 0,
    averageResponseTime: 0,
    topEndpoints: [],
    topErrors: [],
    userActivity: {}
  };
}

告警机制

1. 邮件告警

const nodemailer = require('nodemailer');

async function sendErrorAlert(error, context) {
  const transporter = nodemailer.createTransporter({
    // 邮件服务配置
  });
  
  const mailOptions = {
    from: 'system@jiebanke.com',
    to: 'admin@jiebanke.com',
    subject: `[解班客] 系统错误告警 - ${error.code}`,
    html: `
      <h2>系统错误告警</h2>
      <p><strong>错误代码:</strong> ${error.code}</p>
      <p><strong>错误信息:</strong> ${error.message}</p>
      <p><strong>发生时间:</strong> ${new Date().toLocaleString()}</p>
      <p><strong>请求路径:</strong> ${context.path}</p>
      <p><strong>用户ID:</strong> ${context.userId || '未知'}</p>
      <p><strong>IP地址:</strong> ${context.ip}</p>
      <pre><strong>错误堆栈:</strong>\n${error.stack}</pre>
    `
  };
  
  await transporter.sendMail(mailOptions);
}

2. 钉钉/企业微信告警

async function sendDingTalkAlert(error, context) {
  const webhook = process.env.DINGTALK_WEBHOOK;
  
  const message = {
    msgtype: 'markdown',
    markdown: {
      title: '系统错误告警',
      text: `
### 系统错误告警
- **错误代码**: ${error.code}
- **错误信息**: ${error.message}
- **发生时间**: ${new Date().toLocaleString()}
- **请求路径**: ${context.path}
- **用户ID**: ${context.userId || '未知'}
- **IP地址**: ${context.ip}
      `
    }
  };
  
  await fetch(webhook, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(message)
  });
}

性能优化

日志性能优化

1. 异步日志写入

const winston = require('winston');

const logger = winston.createLogger({
  transports: [
    new winston.transports.File({
      filename: 'logs/app.log',
      // 启用异步写入
      options: { flags: 'a' }
    })
  ]
});

2. 日志缓冲

class LogBuffer {
  constructor(flushInterval = 1000, maxBufferSize = 100) {
    this.buffer = [];
    this.flushInterval = flushInterval;
    this.maxBufferSize = maxBufferSize;
    
    // 定时刷新缓冲区
    setInterval(() => this.flush(), flushInterval);
  }
  
  add(logEntry) {
    this.buffer.push(logEntry);
    
    // 缓冲区满时立即刷新
    if (this.buffer.length >= this.maxBufferSize) {
      this.flush();
    }
  }
  
  flush() {
    if (this.buffer.length === 0) return;
    
    const logs = this.buffer.splice(0);
    // 批量写入日志
    this.writeLogs(logs);
  }
  
  writeLogs(logs) {
    // 实现批量日志写入
  }
}

3. 日志采样

class LogSampler {
  constructor(sampleRate = 0.1) {
    this.sampleRate = sampleRate;
  }
  
  shouldLog(level) {
    // 错误日志始终记录
    if (level === 'error') return true;
    
    // 其他日志按采样率记录
    return Math.random() < this.sampleRate;
  }
}

错误处理性能优化

1. 错误缓存

const errorCache = new Map();

function cacheError(error, context) {
  const key = `${error.code}_${context.path}`;
  const cached = errorCache.get(key);
  
  if (cached && Date.now() - cached.timestamp < 60000) {
    // 1分钟内相同错误不重复处理
    return false;
  }
  
  errorCache.set(key, {
    timestamp: Date.now(),
    count: (cached?.count || 0) + 1
  });
  
  return true;
}

2. 错误聚合

class ErrorAggregator {
  constructor(windowSize = 60000) {
    this.windowSize = windowSize;
    this.errors = new Map();
    
    // 定期清理过期错误
    setInterval(() => this.cleanup(), windowSize);
  }
  
  add(error, context) {
    const key = `${error.code}_${context.path}`;
    const now = Date.now();
    
    if (!this.errors.has(key)) {
      this.errors.set(key, {
        first: now,
        last: now,
        count: 1,
        error,
        context
      });
    } else {
      const entry = this.errors.get(key);
      entry.last = now;
      entry.count++;
    }
  }
  
  cleanup() {
    const now = Date.now();
    for (const [key, entry] of this.errors.entries()) {
      if (now - entry.last > this.windowSize) {
        this.errors.delete(key);
      }
    }
  }
}

使用示例

基础错误处理

1. 控制器中的错误处理

const { AppError, ErrorTypes, catchAsync } = require('../middleware/errorHandler');
const logger = require('../utils/logger');

// 获取用户信息
const getUser = catchAsync(async (req, res, next) => {
  const { userId } = req.params;
  
  // 参数验证
  if (!userId || !mongoose.Types.ObjectId.isValid(userId)) {
    return next(new AppError(
      ErrorTypes.INVALID_FORMAT.message,
      400,
      ErrorTypes.INVALID_FORMAT.code
    ));
  }
  
  // 查询用户
  const user = await User.findById(userId);
  if (!user) {
    return next(new AppError(
      ErrorTypes.RESOURCE_NOT_FOUND.message,
      404,
      ErrorTypes.RESOURCE_NOT_FOUND.code
    ));
  }
  
  // 权限检查
  if (req.user.id !== userId && req.user.role !== 'admin') {
    return next(new AppError(
      ErrorTypes.PERMISSION_DENIED.message,
      403,
      ErrorTypes.PERMISSION_DENIED.code
    ));
  }
  
  // 记录业务日志
  logger.business('查看用户信息', {
    action: 'VIEW_USER',
    targetUserId: userId,
    operatorId: req.user.id,
    ip: req.ip
  });
  
  res.json({
    success: true,
    data: { user }
  });
});

2. 数据库操作错误处理

const { handleDatabaseError } = require('../middleware/errorHandler');

async function createUser(userData) {
  try {
    const user = new User(userData);
    await user.save();
    
    logger.business('用户创建成功', {
      action: 'CREATE_USER',
      userId: user._id,
      email: user.email
    });
    
    return user;
  } catch (error) {
    // 处理数据库错误
    throw handleDatabaseError(error);
  }
}

高级日志记录

1. 请求日志中间件使用

const express = require('express');
const { requestLogger } = require('../utils/logger');

const app = express();

// 使用请求日志中间件
app.use(requestLogger);

// 路由定义
app.get('/api/users', (req, res) => {
  // 业务逻辑
});

2. 性能监控

const logger = require('../utils/logger');

async function performDatabaseQuery(query) {
  const startTime = Date.now();
  
  try {
    const result = await db.query(query);
    const duration = Date.now() - startTime;
    
    // 记录性能日志
    logger.performance('数据库查询', {
      query: query.sql,
      duration,
      rowCount: result.length,
      success: true
    });
    
    // 慢查询告警
    if (duration > 1000) {
      logger.warn('慢查询检测', {
        query: query.sql,
        duration,
        threshold: 1000
      });
    }
    
    return result;
  } catch (error) {
    const duration = Date.now() - startTime;
    
    logger.performance('数据库查询失败', {
      query: query.sql,
      duration,
      error: error.message,
      success: false
    });
    
    throw error;
  }
}

3. 安全事件记录

const logger = require('../utils/logger');

// 登录失败记录
function recordLoginFailure(email, ip, reason) {
  logger.security('登录失败', {
    event: 'LOGIN_FAILED',
    email,
    ip,
    reason,
    timestamp: new Date(),
    severity: 'medium'
  });
}

// 可疑活动记录
function recordSuspiciousActivity(userId, activity, details) {
  logger.security('可疑活动', {
    event: 'SUSPICIOUS_ACTIVITY',
    userId,
    activity,
    details,
    timestamp: new Date(),
    severity: 'high'
  });
}

故障排除

常见问题

1. 日志文件过大

问题: 日志文件增长过快,占用大量磁盘空间 解决方案:

  • 启用日志轮转
  • 调整日志级别
  • 实施日志采样
  • 定期清理旧日志

2. 错误信息泄露

问题: 错误响应包含敏感信息 解决方案:

  • 使用统一错误响应格式
  • 过滤敏感信息
  • 区分开发和生产环境
  • 记录详细日志但返回简化错误

3. 性能影响

问题: 日志记录影响系统性能 解决方案:

  • 使用异步日志写入
  • 实施日志缓冲
  • 优化日志格式
  • 使用日志采样

调试技巧

1. 启用调试日志

// 设置环境变量
NODE_ENV=development
LOG_LEVEL=debug

// 或在代码中动态设置
logger.level = 'debug';

2. 错误追踪

// 添加请求ID用于追踪
const { v4: uuidv4 } = require('uuid');

app.use((req, res, next) => {
  req.requestId = uuidv4();
  res.setHeader('X-Request-ID', req.requestId);
  next();
});

// 在日志中包含请求ID
logger.info('处理请求', {
  requestId: req.requestId,
  method: req.method,
  url: req.url
});

3. 错误重现

// 保存错误上下文用于重现
function saveErrorContext(error, req) {
  const context = {
    timestamp: new Date(),
    error: {
      message: error.message,
      stack: error.stack,
      code: error.code
    },
    request: {
      method: req.method,
      url: req.url,
      headers: req.headers,
      body: req.body,
      params: req.params,
      query: req.query
    },
    user: req.user,
    session: req.session
  };
  
  // 保存到文件或数据库
  fs.writeFileSync(
    `error-contexts/${Date.now()}.json`,
    JSON.stringify(context, null, 2)
  );
}

最佳实践

错误处理最佳实践

  1. 统一错误格式: 使用统一的错误响应格式
  2. 错误分类: 明确区分业务错误和系统错误
  3. 错误码管理: 使用有意义的错误码
  4. 安全考虑: 不在错误响应中暴露敏感信息
  5. 用户友好: 提供用户友好的错误信息

日志记录最佳实践

  1. 结构化日志: 使用JSON格式记录结构化数据
  2. 上下文信息: 记录足够的上下文信息用于调试
  3. 性能考虑: 避免日志记录影响系统性能
  4. 安全性: 不在日志中记录敏感信息
  5. 可搜索性: 使用一致的字段名和格式

监控告警最佳实践

  1. 合理阈值: 设置合理的告警阈值
  2. 告警分级: 区分不同级别的告警
  3. 避免告警疲劳: 防止过多无用告警
  4. 快速响应: 建立快速响应机制
  5. 持续优化: 根据实际情况调整监控策略

总结

错误处理和日志系统是解班客平台稳定运行的重要保障。通过统一的错误处理机制、完善的日志记录功能和实时监控告警,系统能够快速发现和解决问题,提供稳定可靠的服务。

系统采用分层设计,支持多种错误类型和日志级别,提供了灵活的配置选项和丰富的功能特性。通过性能优化和最佳实践,确保系统在高负载情况下仍能正常运行。

未来将继续完善系统功能,增加更多监控指标和告警机制,为平台的稳定运行提供更强有力的支持。