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
+2 -2
View File
@@ -11,12 +11,12 @@ Run once per environment:
docker exec siftlode-worker-1 python -c "from app.downloads import backfill; print(backfill.run())"
"""
import logging
from urllib.parse import urlsplit
import yt_dlp
from sqlalchemy import select
from app import logs
from app.config import settings
from app.db import SessionLocal
from app.downloads import adapters
@@ -24,7 +24,7 @@ from app.downloads import edit as editmod
from app.downloads import formats, service
from app.models import MediaAsset
log = logging.getLogger("siftlode.backfill")
log = logs.get_logger(__name__)
def _extract_meta(url: str, adapter: adapters.SourceAdapter, asset_id: int) -> dict | None:
+2 -2
View File
@@ -23,19 +23,19 @@ Security controls enforced here:
logged, or written to a job's error/snapshot.
"""
import logging
import os
from pathlib import Path
from sqlalchemy import select
from sqlalchemy.orm import Session
from app import logs
from app.downloads import formats
from app.downloads.adapters import _host_is
from app.models import DownloadCredential, MediaAsset
from app.security import decrypt_optional, encrypt
log = logging.getLogger("siftlode.downloads.credentials")
log = logs.get_logger(__name__)
class HostSpec:
+5 -3
View File
@@ -23,7 +23,6 @@ for a player hover-preview later.
import hashlib
import json
import logging
import math
import os
import queue
@@ -33,9 +32,10 @@ import threading
import time
from pathlib import Path
from app import logs
from app.config import settings
log = logging.getLogger("siftlode.edit")
log = logs.get_logger(__name__)
def encode_thread_cap() -> int:
@@ -368,7 +368,9 @@ def escalate_kill(proc: subprocess.Popen, terminate, kill, grace_s: float, label
try:
proc.wait(timeout=grace_s)
except subprocess.TimeoutExpired:
log.error("%s pid %s survived SIGKILL", label, proc.pid)
# noqa TRY400: the exception IS the message here. A TimeoutExpired's traceback is this
# `wait` call and nothing else — it would add three frames and no fact the line lacks.
log.error("%s pid %s survived SIGKILL", label, proc.pid) # noqa: TRY400
def _stop(proc: subprocess.Popen) -> None:
+2 -4
View File
@@ -16,7 +16,6 @@ Five passes each run:
Runs in the API process (which mounts DOWNLOAD_ROOT). Pure disk/DB work — no YouTube quota.
"""
import logging
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
@@ -24,14 +23,13 @@ from pathlib import Path
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app import sysconfig
from app import logs, sysconfig
from app.config import settings
from app.downloads import storage
from app.models import DownloadJob, MediaAsset
from app.notifications import create_notification
log = logging.getLogger("siftlode.downloads.gc")
log = logs.get_logger(__name__)
# Dot-prefixed top-level trees that are NOT part of the Plex/flat asset layout — the orphan sweep
# never touches them (user clips, Plex HLS/img caches, in-progress staging, editor storyboards).
_SYSTEM_TREE = {".edits", ".plex-hls", ".plex-img-cache", ".staging", ".storyboards"}
+2 -3
View File
@@ -1,6 +1,5 @@
"""Child-side download/edit pipeline — the yt-dlp download + ffmpeg edit that runs in the spawned subprocess."""
import logging
import re
import shutil
import time
@@ -9,7 +8,7 @@ from pathlib import Path
import yt_dlp
from app import sysconfig
from app import logs, sysconfig
from app.config import settings
from app.db import SessionLocal
from app.downloads import edit as editmod
@@ -29,7 +28,7 @@ from app.models import DownloadJob, MediaAsset
from app.notifications import create_notification
from app.titles import normalize_title
log = logging.getLogger("siftlode.worker")
log = logs.get_logger(__name__)
class _Aborted(Exception):
+6 -3
View File
@@ -11,7 +11,6 @@ writes the terminal job/asset state itself (done / error, or a cooperative pause
progress hook), then exits 0. An unexpected crash exits non-zero and the parent fails the job.
"""
import logging
import os
import shutil
import subprocess
@@ -24,7 +23,7 @@ from app.db import SessionLocal
from app.downloads import edit as editmod
from app.models import DownloadJob
log = logging.getLogger("siftlode.worker.runner")
log = logs.get_logger(__name__)
def _deprioritize() -> None:
@@ -57,7 +56,11 @@ def main() -> None:
logs.configure_logging("runner")
_deprioritize()
if len(sys.argv) != 2:
print("usage: python -m app.downloads.runner <job_id>", file=sys.stderr)
# noqa T201: this runs BEFORE configure_logging, and a usage error has to reach the
# operator who typed it whether or not logging was ever set up.
print( # noqa: T201
"usage: python -m app.downloads.runner <job_id>", file=sys.stderr
)
raise SystemExit(2)
job_id = int(sys.argv[1])
# This whole process exists to run ONE job — tag every line it prints with it, so the child's