Files
Audiolib/backend/app/main.py
Audiolib 3aab0ac9f1 Add logging system: app.log + matching.log with admin viewer
- Backend: RotatingFileHandler for app.log (all) and matching.log (matcher/matching services)
- New GET/DELETE /api/logs/{app|matching} endpoints (admin-only)
- matcher.py: improved cover-download logging (URL, bytes, HTTP errors, missing cover URL)
- Frontend: Logs tab in Admin panel with log switcher, line count selector, color-coded ERROR/WARNING lines, clear button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:27:54 +02:00

103 lines
3.0 KiB
Python

import logging
import logging.handlers
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from .database import init_db
from .config import get_settings
from .services.file_watcher import start_file_watcher, stop_file_watcher
from .services.podcast_feed import update_all_feeds
def _setup_logging():
settings = get_settings()
os.makedirs(settings.log_dir, exist_ok=True)
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
root = logging.getLogger()
root.setLevel(logging.INFO)
# Console (bestehend)
console = logging.StreamHandler()
console.setFormatter(fmt)
root.addHandler(console)
# app.log — alle Logs
app_fh = logging.handlers.RotatingFileHandler(
os.path.join(settings.log_dir, "app.log"),
maxBytes=5_000_000, backupCount=2, encoding="utf-8"
)
app_fh.setFormatter(fmt)
root.addHandler(app_fh)
# matching.log — nur Matching-bezogene Logger
match_fh = logging.handlers.RotatingFileHandler(
os.path.join(settings.log_dir, "matching.log"),
maxBytes=5_000_000, backupCount=2, encoding="utf-8"
)
match_fh.setFormatter(fmt)
for name in ("app.services.matcher", "app.services.matching"):
logging.getLogger(name).addHandler(match_fh)
_setup_logging()
logger = logging.getLogger(__name__)
_scheduler = AsyncIOScheduler()
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
for d in [settings.hls_cache_dir, settings.covers_dir, settings.log_dir]:
os.makedirs(d, exist_ok=True)
await init_db()
await start_file_watcher()
_scheduler.add_job(update_all_feeds, "interval", hours=settings.podcast_update_interval_hours, id="feed_update")
_scheduler.start()
logger.info("Audiolib gestartet.")
yield
stop_file_watcher()
_scheduler.shutdown(wait=False)
logger.info("Audiolib gestoppt.")
app = FastAPI(title="Audiolib", version="2.4.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
settings = get_settings()
if os.path.exists(settings.covers_dir):
app.mount("/covers", StaticFiles(directory=settings.covers_dir), name="covers")
from .routers import auth, libraries, items, stream, me, users, settings as settings_router
from .routers import matching, podcasts, setup, filebrowser, logs
app.include_router(setup.router)
app.include_router(auth.router)
app.include_router(libraries.router)
app.include_router(items.router)
app.include_router(stream.router)
app.include_router(me.router)
app.include_router(users.router)
app.include_router(settings_router.router)
app.include_router(matching.router)
app.include_router(podcasts.router)
app.include_router(filebrowser.router)
app.include_router(logs.router)