API错误处理 api-error-handling

这个技能涉及构建健壮的API错误处理系统,包括标准化错误响应、详细日志记录、错误分类和用户友好错误消息,以提高API的弹性和错误报告的质量。

后端开发 0 次安装 0 次浏览 更新于 3/3/2026

以下是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
  • 默默忽略错误
  • 记录敏感数据
  • 使用模糊的错误消息
  • 将错误处理与业务逻辑混合
  • 无限期地重试所有错误
  • 暴露内部实现细节
  • 返回不同格式的错误