feat: Add comprehensive logging system with console and file output

- Create logging infrastructure in src/infrastructure/logging/
  - logger.py: Main logging setup with console and file handlers
  - uvicorn_config.py: Custom uvicorn logging configuration
  - __init__.py: Export public logging API

- Update FastAPI application to use logging
  - Replace all print() statements with proper logger calls
  - Initialize logging during application startup
  - Add detailed startup/shutdown logging

- Add startup scripts
  - run_server.py: Python script with uvicorn logging config
  - start_server.sh: Bash wrapper script

- Add comprehensive documentation
  - docs/logging.md: User guide for logging system
  - docs/logging_implementation_summary.md: Technical implementation details

Features:
- Console logging with clean, readable format
- File logging with timestamps to logs/fastapi_app.log
- Configurable log level via LOG_LEVEL environment variable
- Proper lazy formatting for performance
- Captures all uvicorn, application, and module logs
- Automatic log directory creation
This commit is contained in:
2025-10-25 17:40:20 +02:00
parent a41c86f1da
commit 94c53e9555
8 changed files with 560 additions and 12 deletions

View File

@@ -5,6 +5,7 @@ This module provides the main FastAPI application with proper CORS
configuration, middleware setup, static file serving, and Jinja2 template
integration.
"""
import logging
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Optional
@@ -18,6 +19,7 @@ from src.config.settings import settings
# Import core functionality
from src.core.SeriesApp import SeriesApp
from src.infrastructure.logging import setup_logging
from src.server.api.analytics import router as analytics_router
from src.server.api.anime import router as anime_router
from src.server.api.auth import router as auth_router
@@ -51,8 +53,13 @@ from src.server.services.websocket_service import get_websocket_service
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifespan (startup and shutdown)."""
# Setup logging first
logger = setup_logging()
# Startup
try:
logger.info("Starting FastAPI application...")
# Load configuration from config.json and sync with settings
try:
from src.server.services.config_service import get_config_service
@@ -62,25 +69,25 @@ async def lifespan(app: FastAPI):
# Sync anime_directory from config.json to settings
if config.other and config.other.get("anime_directory"):
settings.anime_directory = str(config.other["anime_directory"])
print(
f"Loaded anime_directory from config: "
f"{settings.anime_directory}"
logger.info(
"Loaded anime_directory from config: %s",
settings.anime_directory
)
except Exception as e:
print(f"Warning: Failed to load config from config.json: {e}")
logger.warning("Failed to load config from config.json: %s", e)
# Initialize SeriesApp with configured directory and store it on
# application state so it can be injected via dependencies.
if settings.anime_directory:
app.state.series_app = SeriesApp(settings.anime_directory)
print(
f"SeriesApp initialized with directory: "
f"{settings.anime_directory}"
logger.info(
"SeriesApp initialized with directory: %s",
settings.anime_directory
)
else:
# Log warning when anime directory is not configured
print(
"WARNING: ANIME_DIRECTORY not configured. "
logger.warning(
"ANIME_DIRECTORY not configured. "
"Some features may be unavailable."
)
@@ -100,16 +107,20 @@ async def lifespan(app: FastAPI):
progress_service.set_broadcast_callback(broadcast_callback)
print("FastAPI application started successfully")
logger.info("FastAPI application started successfully")
logger.info("Server running on http://127.0.0.1:8000")
logger.info(
"API documentation available at http://127.0.0.1:8000/api/docs"
)
except Exception as e:
print(f"Error during startup: {e}")
logger.error("Error during startup: %s", e, exc_info=True)
raise # Re-raise to prevent app from starting in broken state
# Yield control to the application
yield
# Shutdown
print("FastAPI application shutting down")
logger.info("FastAPI application shutting down")
def get_series_app() -> Optional[SeriesApp]: