
- Implemented CRUD operations for manga, authors, publishers, and genres - Added search and filtering functionality - Set up SQLAlchemy ORM with SQLite database - Configured Alembic for database migrations - Implemented logging with Loguru - Added comprehensive API documentation - Set up error handling and validation - Added ruff for linting and formatting
87 lines
2.3 KiB
Python
87 lines
2.3 KiB
Python
import logging
|
|
import sys
|
|
|
|
from loguru import logger
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class LogConfig(BaseModel):
|
|
"""Logging configuration"""
|
|
|
|
LOGGER_NAME: str = "manga_inventory_api"
|
|
LOG_FORMAT: str = (
|
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
"<level>{level: <8}</level> | "
|
|
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
|
"<level>{message}</level>"
|
|
)
|
|
LOG_LEVEL: str = "INFO"
|
|
LOG_FILE_PATH: str | None = None
|
|
LOG_ROTATION: str | None = "20 MB"
|
|
LOG_RETENTION: str | None = "1 week"
|
|
LOG_JSON: bool = False
|
|
|
|
|
|
def setup_logging(config: LogConfig = LogConfig()) -> None:
|
|
"""Configure Loguru logger"""
|
|
|
|
# Intercept standard logging
|
|
logging.getLogger().handlers = [InterceptHandler()]
|
|
|
|
# Remove default handlers
|
|
logger.remove()
|
|
|
|
# Add console handler
|
|
logger.add(
|
|
sys.stdout,
|
|
enqueue=True,
|
|
backtrace=True,
|
|
format=config.LOG_FORMAT,
|
|
level=config.LOG_LEVEL,
|
|
colorize=True,
|
|
)
|
|
|
|
# Add file handler if path is specified
|
|
if config.LOG_FILE_PATH:
|
|
logger.add(
|
|
config.LOG_FILE_PATH,
|
|
rotation=config.LOG_ROTATION,
|
|
retention=config.LOG_RETENTION,
|
|
enqueue=True,
|
|
backtrace=True,
|
|
format=config.LOG_FORMAT,
|
|
level=config.LOG_LEVEL,
|
|
colorize=False,
|
|
serialize=config.LOG_JSON,
|
|
)
|
|
|
|
# Configure standard library logging
|
|
for _log in ["uvicorn", "uvicorn.error", "fastapi"]:
|
|
_logger = logging.getLogger(_log)
|
|
_logger.handlers = [InterceptHandler()]
|
|
_logger.propagate = False
|
|
|
|
logger.info("Logging configured successfully")
|
|
|
|
|
|
class InterceptHandler(logging.Handler):
|
|
"""
|
|
Intercept handler for standard library logging to redirect to loguru.
|
|
"""
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
"""
|
|
Intercepts log records and redirects them to loguru.
|
|
"""
|
|
try:
|
|
level = logger.level(record.levelname).name
|
|
except ValueError:
|
|
level = record.levelno
|
|
|
|
frame, depth = logging.currentframe(), 2
|
|
while frame.f_code.co_filename == logging.__file__:
|
|
frame = frame.f_back
|
|
depth += 1
|
|
|
|
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
|