api-error-handling

安装量: 153
排名: #5632

安装

npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill api-error-handling

API Error Handling Overview

Build robust error handling systems with standardized error responses, detailed logging, error categorization, and user-friendly error messages.

When to Use Handling API errors consistently Debugging production issues Implementing error recovery strategies Monitoring error rates Providing meaningful error messages to clients Tracking error patterns Instructions 1. Standardized Error Response Format { "error": { "code": "VALIDATION_ERROR", "message": "Input validation failed", "statusCode": 422, "requestId": "req_abc123xyz789", "timestamp": "2025-01-15T10:30:00Z", "details": [ { "field": "email", "message": "Invalid email format", "code": "INVALID_EMAIL" }, { "field": "age", "message": "Must be at least 18", "code": "VALUE_OUT_OF_RANGE" } ], "path": "/api/users", "method": "POST", "traceId": "trace_001" } }

  1. Node.js Error Handling const express = require('express'); const app = express();

// Error codes and mappings const ERROR_CODES = { VALIDATION_ERROR: { status: 422, message: 'Validation failed' }, NOT_FOUND: { status: 404, message: 'Resource not found' }, UNAUTHORIZED: { status: 401, message: 'Authentication required' }, FORBIDDEN: { status: 403, message: 'Access denied' }, CONFLICT: { status: 409, message: 'Resource conflict' }, RATE_LIMITED: { status: 429, message: 'Too many requests' }, INTERNAL_ERROR: { status: 500, message: 'Internal server error' }, SERVICE_UNAVAILABLE: { status: 503, message: 'Service unavailable' } };

// Custom error class 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(); } }

// Global error handler middleware app.use((err, req, res, next) => { const requestId = req.id || req_${Date.now()}; const traceId = req.traceId;

// Log error logError(err, { requestId, traceId, method: req.method, path: req.path, query: req.query, userId: req.user?.id });

// Handle different error types 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', 'Invalid 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', 'Validation failed', 422, details); return res.status(422).json(formatErrorResponse(apiError, requestId, traceId)); }

if (err.name === 'CastError') { const apiError = new ApiError('NOT_FOUND', 'Invalid resource ID', 404); return res.status(404).json(formatErrorResponse(apiError, requestId, traceId)); }

// Unknown error const internalError = new ApiError('INTERNAL_ERROR', 'An unexpected error occurred', 500); res.status(500).json(formatErrorResponse(internalError, requestId, traceId)); });

// Error response formatter 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 } }; }

// Error logger function logError(error, context) { const logData = { timestamp: new Date().toISOString(), errorCode: error.code, errorMessage: error.message, statusCode: error.statusCode, stack: error.stack, context };

// Log to different levels based on severity if (error.statusCode >= 500) { console.error('[ERROR]', JSON.stringify(logData)); // Send to error tracking service (Sentry, etc) trackError(logData); } else if (error.statusCode >= 400) { console.warn('[WARN]', JSON.stringify(logData)); } }

// Route with error handling app.post('/api/users', async (req, res, next) => { try { const { email, firstName, lastName } = req.body;

// Validation
if (!email || !firstName || !lastName) {
  throw new ApiError(
    'VALIDATION_ERROR',
    'Missing required fields',
    422,
    [
      !email && { field: 'email', message: 'Email is required' },
      !firstName && { field: 'firstName', message: 'First name is required' },
      !lastName && { field: 'lastName', message: 'Last name is required' }
    ].filter(Boolean)
  );
}

// Check for conflicts
const existing = await User.findOne({ email });
if (existing) {
  throw new ApiError('CONFLICT', 'Email already exists', 409);
}

const user = await User.create({ email, firstName, lastName });
res.status(201).json({ data: user });

} catch (error) { next(error); } });

// Async route wrapper 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', 'User not found', 404); }

res.json({ data: user }); }));

// Handle unhandled rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason); trackError({ type: 'unhandledRejection', reason }); });

  1. Python Error Handling (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', 'Invalid request', 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', 'Resource 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'Internal error: {error}', exc_info=True) api_error = APIError('INTERNAL_ERROR', 'Internal server 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', 'Request body required', 400)

errors = []
if not data.get('email'):
    errors.append({'field': 'email', 'message': 'Email is required'})
if not data.get('firstName'):
    errors.append({'field': 'firstName', 'message': 'First name is required'})

if errors:
    raise APIError('VALIDATION_ERROR', 'Validation failed', 422, errors)

try:
    user = User.create(**data)
    return jsonify({'data': user.to_dict()}), 201
except IntegrityError:
    raise APIError('CONFLICT', 'Email already exists', 409)

@app.route('/api/users/') def get_user(user_id): user = User.query.get(user_id) if not user: raise APIError('NOT_FOUND', 'User not found', 404) return jsonify({'data': user.to_dict()})

  1. Error Recovery Strategies // Circuit breaker pattern 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', 'Circuit breaker is open', 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; } } }

// Retry with exponential backoff 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));
}

} }

  1. Error Monitoring // Sentry integration 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 }); }

// Error rate monitoring 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); });

Best Practices ✅ DO Use consistent error response format Include request ID for tracing Log with appropriate severity levels Provide actionable error messages Include error details for debugging Use standard HTTP status codes Implement error recovery strategies Monitor error rates Distinguish user vs server errors Handle all error types ❌ DON'T Expose stack traces to clients Return 200 for errors Ignore errors silently Log sensitive data Use vague error messages Mix error handling with business logic Retry all errors indefinitely Expose internal implementation details Return different formats for errors

返回排行榜