859 lines
20 KiB
Markdown
859 lines
20 KiB
Markdown
|
|
# 错误处理和日志系统文档
|
||
|
|
|
||
|
|
## 概述
|
||
|
|
|
||
|
|
错误处理和日志系统是解班客平台的核心基础设施,提供统一的错误处理机制、完善的日志记录功能和系统监控能力。系统采用分层设计,支持多种错误类型处理、多级日志记录和实时监控。
|
||
|
|
|
||
|
|
## 系统架构
|
||
|
|
|
||
|
|
### 核心组件
|
||
|
|
|
||
|
|
1. **错误处理中间件** (`middleware/errorHandler.js`)
|
||
|
|
- 全局错误捕获
|
||
|
|
- 错误分类处理
|
||
|
|
- 统一错误响应
|
||
|
|
- 错误日志记录
|
||
|
|
|
||
|
|
2. **日志记录系统** (`utils/logger.js`)
|
||
|
|
- 多级日志记录
|
||
|
|
- 日志格式化
|
||
|
|
- 日志轮转管理
|
||
|
|
- 性能监控
|
||
|
|
|
||
|
|
3. **自定义错误类**
|
||
|
|
- 业务错误定义
|
||
|
|
- 错误码管理
|
||
|
|
- 错误信息国际化
|
||
|
|
- 错误堆栈追踪
|
||
|
|
|
||
|
|
## 错误处理机制
|
||
|
|
|
||
|
|
### 错误分类
|
||
|
|
|
||
|
|
#### 1. 业务错误 (Business Errors)
|
||
|
|
- **用户认证错误**: 登录失败、token过期等
|
||
|
|
- **权限错误**: 无权限访问、操作被拒绝等
|
||
|
|
- **数据验证错误**: 参数格式错误、必填项缺失等
|
||
|
|
- **业务逻辑错误**: 余额不足、状态不允许等
|
||
|
|
|
||
|
|
#### 2. 系统错误 (System Errors)
|
||
|
|
- **数据库错误**: 连接失败、查询超时等
|
||
|
|
- **网络错误**: 请求超时、连接中断等
|
||
|
|
- **文件系统错误**: 文件不存在、权限不足等
|
||
|
|
- **第三方服务错误**: API调用失败、服务不可用等
|
||
|
|
|
||
|
|
#### 3. 程序错误 (Programming Errors)
|
||
|
|
- **语法错误**: 代码语法问题
|
||
|
|
- **运行时错误**: 空指针、类型错误等
|
||
|
|
- **内存错误**: 内存溢出、内存泄漏等
|
||
|
|
- **配置错误**: 配置文件错误、环境变量缺失等
|
||
|
|
|
||
|
|
### 错误处理流程
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[请求开始] --> B[业务逻辑处理]
|
||
|
|
B --> C{是否发生错误?}
|
||
|
|
C -->|否| D[正常响应]
|
||
|
|
C -->|是| E[错误捕获]
|
||
|
|
E --> F[错误分类]
|
||
|
|
F --> G[错误日志记录]
|
||
|
|
G --> H[错误响应格式化]
|
||
|
|
H --> I[返回错误响应]
|
||
|
|
D --> J[请求结束]
|
||
|
|
I --> J
|
||
|
|
```
|
||
|
|
|
||
|
|
### 自定义错误类
|
||
|
|
|
||
|
|
#### AppError 类
|
||
|
|
```javascript
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 错误类型定义
|
||
|
|
```javascript
|
||
|
|
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调用频率超限' }
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 错误响应格式
|
||
|
|
|
||
|
|
#### 标准错误响应
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"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"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 验证错误响应
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"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格式日志
|
||
|
|
```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)
|
||
|
|
```javascript
|
||
|
|
// 记录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)
|
||
|
|
```javascript
|
||
|
|
// 记录业务操作
|
||
|
|
logger.business('用户注册', {
|
||
|
|
action: 'USER_REGISTER',
|
||
|
|
userId: 12345,
|
||
|
|
email: 'user@example.com',
|
||
|
|
ip: '192.168.1.100',
|
||
|
|
success: true
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3. 安全日志 (Security Logs)
|
||
|
|
```javascript
|
||
|
|
// 记录安全事件
|
||
|
|
logger.security('登录失败', {
|
||
|
|
event: 'LOGIN_FAILED',
|
||
|
|
email: 'user@example.com',
|
||
|
|
ip: '192.168.1.100',
|
||
|
|
reason: 'INVALID_PASSWORD',
|
||
|
|
attempts: 3
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4. 性能日志 (Performance Logs)
|
||
|
|
```javascript
|
||
|
|
// 记录性能数据
|
||
|
|
logger.performance('数据库查询', {
|
||
|
|
operation: 'SELECT',
|
||
|
|
table: 'users',
|
||
|
|
duration: 50,
|
||
|
|
rowCount: 100,
|
||
|
|
query: 'SELECT * FROM users WHERE status = ?'
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 5. 系统日志 (System Logs)
|
||
|
|
```javascript
|
||
|
|
// 记录系统事件
|
||
|
|
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
|
||
|
|
└── ...
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 日志轮转配置
|
||
|
|
```javascript
|
||
|
|
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. 实时日志分析
|
||
|
|
```javascript
|
||
|
|
// 实时错误统计
|
||
|
|
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. 日志聚合分析
|
||
|
|
```javascript
|
||
|
|
// 按时间段聚合日志
|
||
|
|
function aggregateLogs(startTime, endTime) {
|
||
|
|
return {
|
||
|
|
totalRequests: 0,
|
||
|
|
successRequests: 0,
|
||
|
|
errorRequests: 0,
|
||
|
|
averageResponseTime: 0,
|
||
|
|
topEndpoints: [],
|
||
|
|
topErrors: [],
|
||
|
|
userActivity: {}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 告警机制
|
||
|
|
|
||
|
|
#### 1. 邮件告警
|
||
|
|
```javascript
|
||
|
|
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. 钉钉/企业微信告警
|
||
|
|
```javascript
|
||
|
|
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. 异步日志写入
|
||
|
|
```javascript
|
||
|
|
const winston = require('winston');
|
||
|
|
|
||
|
|
const logger = winston.createLogger({
|
||
|
|
transports: [
|
||
|
|
new winston.transports.File({
|
||
|
|
filename: 'logs/app.log',
|
||
|
|
// 启用异步写入
|
||
|
|
options: { flags: 'a' }
|
||
|
|
})
|
||
|
|
]
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. 日志缓冲
|
||
|
|
```javascript
|
||
|
|
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. 日志采样
|
||
|
|
```javascript
|
||
|
|
class LogSampler {
|
||
|
|
constructor(sampleRate = 0.1) {
|
||
|
|
this.sampleRate = sampleRate;
|
||
|
|
}
|
||
|
|
|
||
|
|
shouldLog(level) {
|
||
|
|
// 错误日志始终记录
|
||
|
|
if (level === 'error') return true;
|
||
|
|
|
||
|
|
// 其他日志按采样率记录
|
||
|
|
return Math.random() < this.sampleRate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 错误处理性能优化
|
||
|
|
|
||
|
|
#### 1. 错误缓存
|
||
|
|
```javascript
|
||
|
|
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. 错误聚合
|
||
|
|
```javascript
|
||
|
|
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. 控制器中的错误处理
|
||
|
|
```javascript
|
||
|
|
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. 数据库操作错误处理
|
||
|
|
```javascript
|
||
|
|
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. 请求日志中间件使用
|
||
|
|
```javascript
|
||
|
|
const express = require('express');
|
||
|
|
const { requestLogger } = require('../utils/logger');
|
||
|
|
|
||
|
|
const app = express();
|
||
|
|
|
||
|
|
// 使用请求日志中间件
|
||
|
|
app.use(requestLogger);
|
||
|
|
|
||
|
|
// 路由定义
|
||
|
|
app.get('/api/users', (req, res) => {
|
||
|
|
// 业务逻辑
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. 性能监控
|
||
|
|
```javascript
|
||
|
|
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. 安全事件记录
|
||
|
|
```javascript
|
||
|
|
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. 启用调试日志
|
||
|
|
```javascript
|
||
|
|
// 设置环境变量
|
||
|
|
NODE_ENV=development
|
||
|
|
LOG_LEVEL=debug
|
||
|
|
|
||
|
|
// 或在代码中动态设置
|
||
|
|
logger.level = 'debug';
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. 错误追踪
|
||
|
|
```javascript
|
||
|
|
// 添加请求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. 错误重现
|
||
|
|
```javascript
|
||
|
|
// 保存错误上下文用于重现
|
||
|
|
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. **持续优化**: 根据实际情况调整监控策略
|
||
|
|
|
||
|
|
## 总结
|
||
|
|
|
||
|
|
错误处理和日志系统是解班客平台稳定运行的重要保障。通过统一的错误处理机制、完善的日志记录功能和实时监控告警,系统能够快速发现和解决问题,提供稳定可靠的服务。
|
||
|
|
|
||
|
|
系统采用分层设计,支持多种错误类型和日志级别,提供了灵活的配置选项和丰富的功能特性。通过性能优化和最佳实践,确保系统在高负载情况下仍能正常运行。
|
||
|
|
|
||
|
|
未来将继续完善系统功能,增加更多监控指标和告警机制,为平台的稳定运行提供更强有力的支持。
|