manus-backend / backend /utils /middleware.py
Manus Deploy
fix: Fix all remaining absolute imports
878c83c
Raw
History Blame Contribute Delete
7.15 kB
"""Error handling, logging, and middleware."""
import logging
import time
import traceback
from typing import Callable
from fastapi import Request, Response
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp
import json
import sys
from backend.core.config import get_settings
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger("manus")
class LoggingMiddleware(BaseHTTPMiddleware):
"""Middleware for logging requests and responses."""
async def dispatch(self, request: Request, call_next: Callable) -> Response:
start_time = time.time()
# Log request
logger.info(f"→ {request.method} {request.url.path}")
# Process request
try:
response = await call_next(request)
# Log response
duration = time.time() - start_time
logger.info(
f"← {request.method} {request.url.path} "
f"[{response.status_code}] {duration:.3f}s"
)
return response
except Exception as e:
duration = time.time() - start_time
logger.error(
f"✗ {request.method} {request.url.path} "
f"[ERROR] {duration:.3f}s - {str(e)}"
)
raise
class RateLimitMiddleware(BaseHTTPMiddleware):
"""Simple rate limiting middleware."""
def __init__(self, app: ASGIApp, max_requests: int = 100, window_seconds: int = 60):
super().__init__(app)
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests: dict[str, list[float]] = {}
async def dispatch(self, request: Request, call_next: Callable) -> Response:
client_ip = request.client.host if request.client else "unknown"
# Clean old requests
now = time.time()
if client_ip in self.requests:
self.requests[client_ip] = [
t for t in self.requests[client_ip]
if now - t < self.window_seconds
]
else:
self.requests[client_ip] = []
# Check rate limit
if len(self.requests[client_ip]) >= self.max_requests:
return JSONResponse(
status_code=429,
content={
"detail": "Too many requests",
"retry_after": self.window_seconds
},
headers={"Retry-After": str(self.window_seconds)}
)
# Record request
self.requests[client_ip].append(now)
return await call_next(request)
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
"""Middleware for handling errors and formatting responses."""
async def dispatch(self, request: Request, call_next: Callable) -> Response:
try:
return await call_next(request)
except Exception as e:
return await self.handle_error(request, e)
async def handle_error(self, request: Request, exc: Exception) -> JSONResponse:
# Log the error
logger.error(f"Error processing {request.method} {request.url.path}: {exc}")
logger.error(traceback.format_exc())
# Handle specific error types
if isinstance(exc, ValueError):
return JSONResponse(
status_code=400,
content={
"error": "Bad Request",
"detail": str(exc),
"code": "VALIDATION_ERROR"
}
)
if isinstance(exc, PermissionError):
return JSONResponse(
status_code=403,
content={
"error": "Forbidden",
"detail": str(exc),
"code": "PERMISSION_DENIED"
}
)
# Default to 500
return JSONResponse(
status_code=500,
content={
"error": "Internal Server Error",
"detail": "An unexpected error occurred" if not settings.DEBUG else str(exc),
"code": "INTERNAL_ERROR"
},
headers={"X-Error-Code": "INTERNAL_ERROR"}
)
class CircuitBreaker:
"""Circuit breaker for external services."""
def __init__(self, failure_threshold: int = 5, recovery_timeout: int = 60):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.failures = 0
self.last_failure_time = 0
self.state = "closed" # closed, open, half-open
def call(self, func, *args, **kwargs):
"""Execute function with circuit breaker."""
if self.state == "open":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "half-open"
else:
raise Exception("Circuit breaker is open")
try:
result = func(*args, **kwargs)
if self.state == "half-open":
self.state = "closed"
self.failures = 0
return result
except Exception as e:
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.failure_threshold:
self.state = "open"
logger.warning(f"Circuit breaker opened after {self.failures} failures")
raise e
def reset(self):
"""Reset the circuit breaker."""
self.failures = 0
self.state = "closed"
# Global circuit breakers
ai_gateway_circuit = CircuitBreaker(failure_threshold=3, recovery_timeout=30)
e2b_circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=60)
redis_circuit = CircuitBreaker(failure_threshold=3, recovery_timeout=30)
def setup_middleware(app):
"""Configure all middleware for the application."""
settings = get_settings()
# Add logging middleware
app.add_middleware(LoggingMiddleware)
# Add rate limiting in production
if not settings.DEBUG:
app.add_middleware(
RateLimitMiddleware,
max_requests=100,
window_seconds=60
)
# Add error handling
app.add_middleware(ErrorHandlerMiddleware)
def log_error(component: str, error: Exception, context: dict = None):
"""Log an error with context."""
logger.error(f"[{component}] {type(error).__name__}: {str(error)}")
if context:
logger.error(f"Context: {json.dumps(context)}")
logger.error(traceback.format_exc())
def log_info(component: str, message: str, context: dict = None):
"""Log an info message with context."""
logger.info(f"[{component}] {message}")
if context:
logger.info(f"Context: {json.dumps(context)}")