以下是API错误处理的中文翻译:
名称:api-error-handling 描述:实现全面的API错误处理,包括标准化的错误响应、日志记录、监控和用户友好的消息。在构建弹性API、调试问题、改进错误报告时使用。
API错误处理
概览
构建具有标准化错误响应、详细日志记录、错误分类和用户友好错误消息的健壮错误处理系统。
使用场景
- 一致性地处理API错误
- 调试生产问题
- 实施错误恢复策略
- 监控错误率
- 向客户端提供有意义的错误消息
- 跟踪错误模式
指令
1. 标准化错误响应格式
{
"error": {
"code": "VALIDATION_ERROR",
"message": "输入验证失败",
"statusCode": 422,
"requestId": "req_abc123xyz789",
"timestamp": "2025-01-15T10:30:00Z",
"details": [
{
"field": "email",
"message": "无效的电子邮件格式",
"code": "INVALID_EMAIL"
},
{
"field": "age",
"message": "必须至少18岁",
"code": "VALUE_OUT_OF_RANGE"
}
],
"path": "/api/users",
"method": "POST",
"traceId": "trace_001"
}
}
2. Node.js错误处理
const express = require('express');
const app = express();
// 错误代码和映射
const ERROR_CODES = {
VALIDATION_ERROR: { status: 422, message: '验证失败' },
NOT_FOUND: { status: 404, message: '资源未找到' },
UNAUTHORIZED: { status: 401, message: '需要认证' },
FORBIDDEN: { status: 403, message: '访问被拒绝' },
CONFLICT: { status: 409, message: '资源冲突' },
RATE_LIMITED: { status: 429, message: '请求过多' },
INTERNAL_ERROR: { status: 500, message: '内部服务器错误' },
SERVICE_UNAVAILABLE: { status: 503, message: '服务不可用' }
};
// 自定义错误类
class ApiError extends Error {
constructor(code, message, statusCode = null, details = null) {
super(message);
this.code = code;
this.statusCode = statusCode || ERROR_CODES[code]?.status || 500;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
// 全局错误处理中间件
app.use((err, req, res, next) => {
const requestId = req.id || `req_${Date.now()}`;
const traceId = req.traceId;
// 记录错误
logError(err, {
requestId,
traceId,
method: req.method,
path: req.path,
query: req.query,
userId: req.user?.id
});
// 处理不同类型的错误
if (err instanceof ApiError) {
return res.status(err.statusCode).json(formatErrorResponse(err, requestId, traceId));
}
if (err instanceof SyntaxError && 'body' in err) {
const apiError = new ApiError('VALIDATION_ERROR', '无效的JSON', 400);
return res.status(400).json(formatErrorResponse(apiError, requestId, traceId));
}
if (err.name === 'ValidationError') {
const details = Object.keys(err.errors).map(field => ({
field,
message: err.errors[field].message,
code: 'VALIDATION_FAILED'
}));
const apiError = new ApiError('VALIDATION_ERROR', '验证失败', 422, details);
return res.status(422).json(formatErrorResponse(apiError, requestId, traceId));
}
if (err.name === 'CastError') {
const apiError = new ApiError('NOT_FOUND', '无效的资源ID', 404);
return res.status(404).json(formatErrorResponse(apiError, requestId, traceId));
}
// 未知错误
const internalError = new ApiError('INTERNAL_ERROR', '发生了一个意外的错误', 500);
res.status(500).json(formatErrorResponse(internalError, requestId, traceId));
});
// 错误响应格式化器
function formatErrorResponse(error, requestId, traceId) {
return {
error: {
code: error.code,
message: error.message,
statusCode: error.statusCode,
requestId,
timestamp: error.timestamp,
...(error.details && { details: error.details }),
traceId
}
};
}
// 错误记录器
function logError(error, context) {
const logData = {
timestamp: new Date().toISOString(),
errorCode: error.code,
errorMessage: error.message,
statusCode: error.statusCode,
stack: error.stack,
context
};
// 根据严重性记录不同级别
if (error.statusCode >= 500) {
console.error('[ERROR]', JSON.stringify(logData));
// 发送到错误跟踪服务(Sentry等)
trackError(logData);
} else if (error.statusCode >= 400) {
console.warn('[WARN]', JSON.stringify(logData));
}
}
// 带错误处理的路由
app.post('/api/users', async (req, res, next) => {
try {
const { email, firstName, lastName } = req.body;
// 验证
if (!email || !firstName || !lastName) {
throw new ApiError(
'VALIDATION_ERROR',
'缺少必填字段',
422,
[
!email && { field: 'email', message: '电子邮件是必填的' },
!firstName && { field: 'firstName', message: '名字是必填的' },
!lastName && { field: 'lastName', message: '姓氏是必填的' }
].filter(Boolean)
);
}
// 检查冲突
const existing = await User.findOne({ email });
if (existing) {
throw new ApiError('CONFLICT', '电子邮件已存在', 409);
}
const user = await User.create({ email, firstName, lastName });
res.status(201).json({ data: user });
} catch (error) {
next(error);
}
});
// 异步路由包装器
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new ApiError('NOT_FOUND', '用户未找到', 404);
}
res.json({ data: user });
}));
// 处理未处理的拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的拒绝:', reason);
trackError({ type: 'unhandledRejection', reason });
});
3. Python错误处理(Flask)
from flask import Flask, jsonify, request
from datetime import datetime
import logging
import traceback
from functools import wraps
app = Flask(__name__)
logger = logging.getLogger(__name__)
class APIError(Exception):
def __init__(self, code, message, status_code=500, details=None):
super().__init__()
self.code = code
self.message = message
self.status_code = status_code
self.details = details or []
self.timestamp = datetime.utcnow().isoformat()
ERROR_CODES = {
'VALIDATION_ERROR': 422,
'NOT_FOUND': 404,
'UNAUTHORIZED': 401,
'FORBIDDEN': 403,
'CONFLICT': 409,
'INTERNAL_ERROR': 500
}
def format_error(error, request_id, trace_id):
return {
'error': {
'code': error.code,
'message': error.message,
'statusCode': error.status_code,
'requestId': request_id,
'timestamp': error.timestamp,
'traceId': trace_id,
'details': error.details if error.details else None
}
}
@app.errorhandler(APIError)
def handle_api_error(error):
request_id = request.headers.get('X-Request-ID', f'req_{int(datetime.utcnow().timestamp())}')
trace_id = request.headers.get('X-Trace-ID')
log_error(error, {
'request_id': request_id,
'trace_id': trace_id,
'method': request.method,
'path': request.path
})
response = jsonify(format_error(error, request_id, trace_id))
return response, error.status_code
@app.errorhandler(400)
def handle_bad_request(error):
request_id = f'req_{int(datetime.utcnow().timestamp())}'
api_error = APIError('VALIDATION_ERROR', '无效的请求', 400)
return jsonify(format_error(api_error, request_id, None)), 400
@app.errorhandler(404)
def handle_not_found(error):
request_id = f'req_{int(datetime.utcnow().timestamp())}'
api_error = APIError('NOT_FOUND', '资源未找到', 404)
return jsonify(format_error(api_error, request_id, None)), 404
@app.errorhandler(500)
def handle_internal_error(error):
request_id = f'req_{int(datetime.utcnow().timestamp())}'
logger.error(f'内部错误: {error}', exc_info=True)
api_error = APIError('INTERNAL_ERROR', '内部服务器错误', 500)
return jsonify(format_error(api_error, request_id, None)), 500
def log_error(error, context):
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'code': error.code,
'message': error.message,
'status': error.status_code,
'context': context
}
if error.status_code >= 500:
logger.error(log_entry)
elif error.status_code >= 400:
logger.warning(log_entry)
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data:
raise APIError('VALIDATION_ERROR', '请求体是必需的', 400)
errors = []
if not data.get('email'):
errors.append({'field': 'email', 'message': '电子邮件是必需的'})
if not data.get('firstName'):
errors.append({'field': 'firstName', 'message': '名字是必需的'})
if errors:
raise APIError('VALIDATION_ERROR', '验证失败', 422, errors)
try:
user = User.create(**data)
return jsonify({'data': user.to_dict()}), 201
except IntegrityError:
raise APIError('CONFLICT', '电子邮件已存在', 409)
@app.route('/api/users/<user_id>')
def get_user(user_id):
user = User.query.get(user_id)
if not user:
raise APIError('NOT_FOUND', '用户未找到', 404)
return jsonify({'data': user.to_dict()})
4. 错误恢复策略
// 断路器模式
class CircuitBreaker {
constructor(failureThreshold = 5, timeout = 60000) {
this.failureCount = 0;
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new ApiError('SERVICE_UNAVAILABLE', '断路器已打开', 503);
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
// 带指数退避的重试
async function retryWithBackoff(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
5. 错误监控
// Sentry集成
const Sentry = require('@sentry/node');
Sentry.init({ dsn: process.env.SENTRY_DSN });
function trackError(errorData) {
Sentry.captureException(new Error(errorData.errorMessage), {
tags: {
code: errorData.errorCode,
status: errorData.statusCode
},
extra: errorData.context
});
}
// 错误率监控
const errorMetrics = {
total: 0,
byCode: {},
byStatus: {}
};
function recordError(error) {
errorMetrics.total++;
errorMetrics.byCode[error.code] = (errorMetrics.byCode[error.code] || 0) + 1;
errorMetrics.byStatus[error.statusCode] = (errorMetrics.byStatus[error.statusCode] || 0) + 1;
}
app.get('/metrics/errors', (req, res) => {
res.json(errorMetrics);
});
最佳实践
✅ 做
- 使用一致的错误响应格式
- 包含请求ID以进行跟踪
- 用适当的严重性级别记录
- 提供可操作的错误消息
- 包含错误详情以进行调试
- 使用标准HTTP状态代码
- 实施错误恢复策略
- 监控错误率
- 区分用户与服务器错误
- 处理所有错误类型
❌ 不做
- 向客户端暴露堆栈跟踪
- 为错误返回200
- 默默忽略错误
- 记录敏感数据
- 使用模糊的错误消息
- 将错误处理与业务逻辑混合
- 无限期地重试所有错误
- 暴露内部实现细节
- 返回不同格式的错误