- 217 error returns across 18 route files + api.py now use JSONResponse with appropriate HTTP status codes instead of returning HTTP 200 - Status code distribution: 500 (121), 400 (39), 503 (28), 404 (24), 409 (3), 502 (2) - Fixed language.py tuple-return bug (was serializing as JSON array) - Fixed bare except clauses in bipolar_mode.py and voice.py - Body-level error schemas preserved (status/error + success/error patterns) so web UI continues working without changes - chat.py (SSE) unchanged: errors sent within stream protocol - All 170 tests pass
130 lines
4.6 KiB
Python
130 lines
4.6 KiB
Python
# api.py — Thin orchestrator (routes live in routes/*.py)
|
|
#
|
|
# Monolith backup: api_monolith_backup.py
|
|
# To revert: cp api_monolith_backup.py api.py
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from utils.logger import get_logger
|
|
from utils.log_config import load_config as load_log_config
|
|
import time
|
|
from fnmatch import fnmatch
|
|
import nest_asyncio
|
|
nest_asyncio.apply()
|
|
|
|
# Initialize API logger
|
|
logger = get_logger('api')
|
|
api_requests_logger = get_logger('api.requests')
|
|
|
|
app = FastAPI()
|
|
|
|
# ========== Global Exception Handler ==========
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
"""Catch all unhandled exceptions and log them properly."""
|
|
logger.error(f"Unhandled exception on {request.method} {request.url.path}: {exc}", exc_info=True)
|
|
return JSONResponse(status_code=500, content={"success": False, "error": "Internal server error"})
|
|
|
|
# ========== Logging Middleware ==========
|
|
@app.middleware("http")
|
|
async def log_requests(request: Request, call_next):
|
|
"""Middleware to log HTTP requests based on configuration."""
|
|
start_time = time.time()
|
|
|
|
# Get logging config
|
|
log_config = load_log_config()
|
|
api_config = log_config.get('components', {}).get('api.requests', {})
|
|
|
|
# Check if API request logging is enabled
|
|
if not api_config.get('enabled', False):
|
|
return await call_next(request)
|
|
|
|
# Get filters
|
|
filters = api_config.get('filters', {})
|
|
exclude_paths = filters.get('exclude_paths', [])
|
|
exclude_status = filters.get('exclude_status', [])
|
|
include_slow_requests = filters.get('include_slow_requests', True)
|
|
slow_threshold_ms = filters.get('slow_threshold_ms', 1000)
|
|
|
|
# Process request
|
|
response = await call_next(request)
|
|
|
|
# Calculate duration
|
|
duration_ms = (time.time() - start_time) * 1000
|
|
|
|
# Check if path should be excluded
|
|
path = request.url.path
|
|
for pattern in exclude_paths:
|
|
if fnmatch(path, pattern):
|
|
return response
|
|
|
|
# Check if status should be excluded (unless it's a slow request)
|
|
is_slow = duration_ms >= slow_threshold_ms
|
|
if response.status_code in exclude_status and not (include_slow_requests and is_slow):
|
|
return response
|
|
|
|
# Log the request
|
|
log_msg = f"{request.method} {path} - {response.status_code} ({duration_ms:.2f}ms)"
|
|
|
|
if is_slow:
|
|
api_requests_logger.warning(f"SLOW REQUEST: {log_msg}")
|
|
elif response.status_code >= 500:
|
|
api_requests_logger.error(log_msg)
|
|
elif response.status_code >= 400:
|
|
api_requests_logger.warning(log_msg)
|
|
else:
|
|
api_requests_logger.api(log_msg)
|
|
|
|
return response
|
|
|
|
# Serve static folder
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
# ========== Include Route Modules ==========
|
|
from routes.core import router as core_router
|
|
from routes.mood import router as mood_router
|
|
from routes.language import router as language_router
|
|
from routes.evil_mode import router as evil_mode_router
|
|
from routes.bipolar_mode import router as bipolar_mode_router
|
|
from routes.gpu import router as gpu_router
|
|
from routes.bot_actions import router as bot_actions_router
|
|
from routes.autonomous import router as autonomous_router
|
|
from routes.profile_picture import router as profile_picture_router
|
|
from routes.manual_send import router as manual_send_router
|
|
from routes.servers import router as servers_router
|
|
from routes.figurines import router as figurines_router
|
|
from routes.dms import router as dms_router
|
|
from routes.image_generation import router as image_generation_router
|
|
from routes.chat import router as chat_router
|
|
from routes.config import router as config_router
|
|
from routes.logging_config import router as logging_config_router
|
|
from routes.voice import router as voice_router
|
|
from routes.memory import router as memory_router
|
|
|
|
app.include_router(core_router)
|
|
app.include_router(mood_router)
|
|
app.include_router(language_router)
|
|
app.include_router(evil_mode_router)
|
|
app.include_router(bipolar_mode_router)
|
|
app.include_router(gpu_router)
|
|
app.include_router(bot_actions_router)
|
|
app.include_router(autonomous_router)
|
|
app.include_router(profile_picture_router)
|
|
app.include_router(manual_send_router)
|
|
app.include_router(servers_router)
|
|
app.include_router(figurines_router)
|
|
app.include_router(dms_router)
|
|
app.include_router(image_generation_router)
|
|
app.include_router(chat_router)
|
|
app.include_router(config_router)
|
|
app.include_router(logging_config_router)
|
|
app.include_router(voice_router)
|
|
app.include_router(memory_router)
|
|
|
|
|
|
|
|
def start_api():
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=3939)
|