Add live logs to admin dashboard with level filtering and error middleware

This commit is contained in:
root
2026-03-31 15:54:21 -05:00
parent 40322e8861
commit a0d9f1f9d9
4 changed files with 153 additions and 6 deletions

View File

@@ -1,6 +1,8 @@
import logging
import io
from datetime import datetime, timezone, timedelta
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
@@ -15,6 +17,39 @@ router = APIRouter(prefix="/admin", tags=["admin"])
ADMIN_EMAILS = ["chris.ryan@deepcutsai.com"]
# In-memory log buffer for admin viewing
LOG_BUFFER_SIZE = 500
_log_buffer: list[dict] = []
class AdminLogHandler(logging.Handler):
"""Captures log records into an in-memory buffer for the admin UI."""
def emit(self, record):
entry = {
"timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": self.format(record),
}
_log_buffer.append(entry)
if len(_log_buffer) > LOG_BUFFER_SIZE:
_log_buffer.pop(0)
# Attach handler to root logger and uvicorn
_handler = AdminLogHandler()
_handler.setLevel(logging.INFO)
_handler.setFormatter(logging.Formatter("%(message)s"))
logging.getLogger().addHandler(_handler)
logging.getLogger("uvicorn.access").addHandler(_handler)
logging.getLogger("uvicorn.error").addHandler(_handler)
logging.getLogger("app").addHandler(_handler)
# Create app logger for explicit logging
app_logger = logging.getLogger("app")
app_logger.setLevel(logging.INFO)
@router.get("/stats")
async def get_stats(
@@ -98,3 +133,19 @@ async def get_stats(
},
"user_breakdown": user_breakdown,
}
@router.get("/logs")
async def get_logs(
user: User = Depends(get_current_user),
level: str = Query("ALL", description="Filter by level: ALL, ERROR, WARNING, INFO"),
limit: int = Query(100, description="Number of log entries"),
):
if user.email not in ADMIN_EMAILS:
raise HTTPException(status_code=403, detail="Admin only")
logs = _log_buffer[-limit:]
if level != "ALL":
logs = [l for l in logs if l["level"] == level.upper()]
return {"logs": list(reversed(logs)), "total": len(_log_buffer)}

View File

@@ -1,5 +1,9 @@
from fastapi import FastAPI
import logging
import traceback
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app.core.config import settings
from app.api.endpoints import admin, auth, bandcamp, billing, lastfm, manual_import, playlist_fix, playlists, profile, recommendations, youtube_music
@@ -27,6 +31,21 @@ app.include_router(bandcamp.router, prefix="/api")
app.include_router(profile.router, prefix="/api")
logger = logging.getLogger("app")
@app.middleware("http")
async def log_errors(request: Request, call_next):
try:
response = await call_next(request)
if response.status_code >= 400:
logger.warning(f"{request.method} {request.url.path} -> {response.status_code}")
return response
except Exception as e:
logger.error(f"{request.method} {request.url.path} -> 500: {e}\n{traceback.format_exc()}")
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
@app.get("/api/health")
async def health():
return {"status": "ok", "app": "vynl"}