Files
MediaManager-maxdorninger-1/media_manager/logging.py
Maximilian Dorninger f5253990e0 switch from APScheduler to Taskiq (#461)
This PR replaces the APScheduler lib with the Taskiq task queuing lib. 

# why

APScheduler doesn't support FastAPI's DI in tasks, this makes them quite
cumbersome to read and write since DB, Repositories and Services all
need to be instanciated manually.

Moreover, Taskiq makes it easier to start background tasks from FastAPI
requests. This enables MM to move to a more event-based architecture.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* App now uses an orchestrated async startup/shutdown and runs
background scheduling via a database-backed task queue; startup enqueues
pre-load/import/update tasks.

* **Bug Fixes**
* Improved torrent client handling with clearer conflict messages and
guidance for manual resolution.
* Enhanced logging around season, episode and metadata update
operations; minor regex/behaviour formatting preserved.

* **Chores**
* Updated dependencies to support the new task queue and connection
pooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-26 21:23:24 +01:00

87 lines
2.8 KiB
Python

import logging
import os
import sys
from datetime import UTC, datetime
from logging.config import dictConfig
from pathlib import Path
from typing import override
from pythonjsonlogger.json import JsonFormatter
class ISOJsonFormatter(JsonFormatter):
@override
def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
dt = datetime.fromtimestamp(record.created, tz=UTC)
return dt.isoformat(timespec="milliseconds").replace("+00:00", "Z")
LOG_LEVEL = os.getenv("MEDIAMANAGER_LOG_LEVEL", "INFO").upper()
LOG_FILE = Path(os.getenv("LOG_FILE", "/app/config/media_manager.log"))
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"correlation_id": {
"()": "asgi_correlation_id.CorrelationIdFilter",
"uuid_length": 32,
"default_value": "-",
},
},
"formatters": {
"default": {
"format": "%(asctime)s - [%(correlation_id)s] %(levelname)s - %(name)s - %(funcName)s(): %(message)s"
},
"json": {
"()": ISOJsonFormatter,
"format": "%(asctime)s %(correlation_id)s %(levelname)s %(name)s %(message)s",
"rename_fields": {
"levelname": "level",
"asctime": "timestamp",
"name": "module",
},
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
"filters": ["correlation_id"],
"stream": sys.stdout,
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "json",
"filters": ["correlation_id"],
"filename": str(LOG_FILE),
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf-8",
},
},
"root": {
"level": LOG_LEVEL,
"handlers": ["console", "file"],
},
"loggers": {
"uvicorn": {"handlers": ["console", "file"], "level": "DEBUG"},
"uvicorn.access": {"handlers": ["console", "file"], "level": "DEBUG"},
"fastapi": {"handlers": ["console", "file"], "level": "DEBUG"},
},
}
def setup_logging() -> None:
dictConfig(LOGGING_CONFIG)
logging.basicConfig(
level=LOG_LEVEL,
format="%(asctime)s - %(levelname)s - %(name)s - %(funcName)s(): %(message)s",
stream=sys.stdout,
)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("transmission_rpc").setLevel(logging.WARNING)
logging.getLogger("qbittorrentapi").setLevel(logging.WARNING)
logging.getLogger("sabnzbd_api").setLevel(logging.WARNING)
logging.getLogger("taskiq").setLevel(logging.WARNING)