refactor(logs): name every logger after its module, and hold it there

25 literal names covered 34 modules. plex/client.py, plex/stream.py,
plex/subtitles.py, plex/sync.py and two Plex route modules all called themselves
`siftlode.plex`; downloads/pipeline.py called itself `siftlode.worker`;
routes/channels.py called itself `siftlode.api`. The `logger` column is in every
stored row so E22.6's console can filter on it, and so an operator can pin a level
to one part of the system — neither works when six modules share a name.

`logs.get_logger(__name__)` derives it: app/plex/sync.py -> siftlode.plex.sync.
One name per module, and it cannot drift from the file it sits in.
tests/test_logger_names.py pins the derivation, that everything stays under the
one `siftlode` tree the handler and the tests attach to, and that no module
hand-rolls a name again unless the line says why (`# logger-name-ok: <why>`).
Two do: get_logger's own call, and `siftlode.http`, which is named for the traffic
it carries rather than the module it lives in — it replaced uvicorn's access log
and is what an operator pins when they want request lines and nothing else.

app/scheduler.py called its logger `logger`; every other module calls it `log`.

ruff gains LOG, G, T20, TRY400, TRY401 and S112. Four findings cleared with them:
worker.py logged `str(exc)[:120]` where the traceback was the whole question, and
three prints/f-strings that are deliberate now say so on the line. S110 and BLE001
stay off until S3 — 23 handlers are outstanding and each is a judgement about one
of them; enabling the rules first would just mean 23 blanket noqas.
This commit is contained in:
2026-09-18 00:57:57 +02:00
parent dc221338be
commit 0cff964ff2
37 changed files with 204 additions and 116 deletions
+10 -12
View File
@@ -2,7 +2,6 @@
recent-first / deep backfill. Runs inside the API process (single worker)."""
import contextvars
import logging
import threading
from datetime import datetime, timezone
from typing import Callable
@@ -10,7 +9,7 @@ from typing import Callable
from apscheduler.schedulers.background import BackgroundScheduler
from sqlalchemy import select
from app import audit, logsink, progress, quota
from app import audit, logs, logsink, progress, quota
from app.audit import AuditAction
from app.config import settings
from app.db import SessionLocal
@@ -35,8 +34,7 @@ from app.sync.runner import (
run_subscription_resync,
)
logger = logging.getLogger("siftlode.scheduler")
log = logs.get_logger(__name__)
_scheduler: BackgroundScheduler | None = None
# Per-job run activity, kept in-memory for the admin Scheduler dashboard to read (the
@@ -104,7 +102,7 @@ def apply_interval(db, job_id: str, minutes: int) -> int:
db.commit()
if _scheduler is not None and _scheduler.get_job(job_id) is not None:
_scheduler.reschedule_job(job_id, trigger="interval", minutes=minutes)
logger.info("rescheduled job %s to every %s min", job_id, minutes)
log.info("rescheduled job %s to every %s min", job_id, minutes)
return minutes
@@ -131,7 +129,7 @@ def _notify_done(db, actor_id: int, job_id: str, status: str, summary: str | Non
)
except Exception:
db.rollback()
logger.exception("failed to write completion notification for job %s", job_id)
log.exception("failed to write completion notification for job %s", job_id)
def _run_changed(result) -> bool:
@@ -167,7 +165,7 @@ def _audit_run(db, actor_id, job_id, status, summary, result) -> None:
db.commit()
except Exception:
db.rollback()
logger.exception("failed to write audit row for job %s", job_id)
log.exception("failed to write audit row for job %s", job_id)
def _job(name: str, fn) -> None:
@@ -175,7 +173,7 @@ def _job(name: str, fn) -> None:
# HERE, atomically: whichever reaches this first wins, the other skips. This is the real
# single-run guard — trigger_job's pre-check is only a fast best-effort answer for the UI.
if not _claim_running(name):
logger.info("job %s already running — skipping this trigger", name)
log.info("job %s already running — skipping this trigger", name)
return
db = SessionLocal()
actor_id = _manual_actor.get() # set only for a manual "run now" trigger
@@ -188,7 +186,7 @@ def _job(name: str, fn) -> None:
try:
if is_sync_paused(db):
logger.info("job %s skipped (sync paused)", name)
log.info("job %s skipped (sync paused)", name)
_record(
name,
status="skipped",
@@ -203,14 +201,14 @@ def _job(name: str, fn) -> None:
with progress.bind(sink):
result = fn(db)
summary = _summarize(result)
logger.info("job %s -> %s", name, result)
log.info("job %s -> %s", name, result)
_record(name, status="ok", last_finished=_now_iso(), last_result=summary, progress=None)
if actor_id is not None:
_notify_done(db, actor_id, name, "ok", summary)
_audit_run(db, actor_id, name, "ok", summary, result)
except Exception as exc:
db.rollback()
logger.exception("job %s failed", name)
log.exception("job %s failed", name)
err = str(exc) or exc.__class__.__name__
_record(name, status="error", last_finished=_now_iso(), last_error=err, progress=None)
if actor_id is not None:
@@ -493,7 +491,7 @@ def start_scheduler() -> None:
scheduler.add_job(fn, "interval", minutes=iv[job_id], id=job_id)
scheduler.start()
_scheduler = scheduler
logger.info("scheduler started")
log.info("scheduler started")
def shutdown_scheduler() -> None: