feat: add error_summary() helper to replace verbose exception tracebacks with concise German error messages

Add error_summary() function to format exceptions as single-line log messages with 240-char limit and special handling for requests.RequestException as "Dienst nicht erreichbar". Replace log.exception() calls with log.error() using error_summary() across service_monitor, scan_datastore_storage, sab_downloads, scan_one, start_scan, index, downloads, system, and datastore endpoints. Update user-facing error messages to German "momentan nicht verfügbar/
This commit is contained in:
2026-08-16 17:31:21 +02:00
parent 5723a5d2a0
commit 71752483f6
+22 -15
View File
@@ -50,6 +50,13 @@ LOG_RETENTION_HOURS = max(1, int(os.environ.get("LOG_RETENTION_HOURS", "6")))
logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO").upper(), format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("media-max")
def error_summary(exc):
"""Return one short, useful log line instead of a connection traceback."""
if isinstance(exc, requests.RequestException):
return f"{exc.__class__.__name__}: Dienst nicht erreichbar"
return " ".join(str(exc).split())[:240] or exc.__class__.__name__
try:
file_handler = TimedRotatingFileHandler(LOG_FILE, when="H", interval=LOG_RETENTION_HOURS, backupCount=1, encoding="utf-8", delay=True, utc=True)
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
@@ -147,8 +154,8 @@ def service_monitor():
while True:
try:
refresh_services()
except Exception:
log.exception("Dienststatus konnte nicht aktualisiert werden")
except Exception as exc:
log.error("Dienststatus konnte nicht aktualisiert werden: %s", error_summary(exc))
time.sleep(max(5, SERVICE_REFRESH_SECONDS))
threading.Thread(target=service_monitor, name="service-monitor", daemon=True).start()
@@ -237,8 +244,8 @@ def scan_datastore_storage():
category["files"] += 1
usage = shutil.disk_usage(root)
except OSError as exc:
log.exception("Datastore konnte nicht gelesen werden: %s", DATASTORE_PATH)
return {"error": f"Datastore konnte nicht gelesen werden: {exc}", "path": DATASTORE_PATH, "categories": list(categories.values())}
log.error("Datastore konnte nicht gelesen werden (%s): %s", DATASTORE_PATH, error_summary(exc))
return {"error": "Datastore momentan nicht verfügbar", "path": DATASTORE_PATH, "categories": list(categories.values())}
used = usage.used
values = []
for category in categories.values():
@@ -552,8 +559,8 @@ def sab_downloads():
try:
if RADARR_API_KEY: wanted.extend({"title": x.get("movie", {}).get("title") or x.get("title", ""), "source": "Radarr"} for x in api_get(RADARR_URL, RADARR_API_KEY, "queue?includeUnknownMovieItems=true").get("records", []))
if SONARR_URL and SONARR_API_KEY: wanted.extend({"title": x.get("title", ""), "source": "Sonarr"} for x in api_get(SONARR_URL, SONARR_API_KEY, "queue?includeUnknownSeriesItems=true").get("records", []))
except Exception:
log.warning("Radarr/Sonarr-Queue konnte für SAB-Zuordnung nicht geladen werden", exc_info=True)
except Exception as exc:
log.warning("Radarr/Sonarr-Queue konnte für SAB-Zuordnung nicht geladen werden: %s", error_summary(exc))
def make_item(item, completed=False):
title = item.get("name") or item.get("filename") or item.get("nzb_name") or ""
@@ -649,7 +656,7 @@ def scan_one(path):
if not p.exists(): data = {"error": f"Datei nicht gefunden: {p}"}
else:
try: data = run_ffprobe(str(p)); log.info("Gescannt: %s", p)
except Exception as exc: data = {"error": str(exc)}; log.exception("Scanfehler für %s", p)
except Exception as exc: data = {"error": str(exc)}; log.error("Scanfehler für %s: %s", p, error_summary(exc))
mtime = p.stat().st_mtime
con = db(); con.execute("INSERT INTO media_cache(path,mtime,data,scanned_at) VALUES(?,?,?,?) ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime,data=excluded.data,scanned_at=excluded.scanned_at", (str(p), mtime, json.dumps(data), datetime.utcnow().isoformat())); con.commit(); con.close()
return data
@@ -751,7 +758,7 @@ def start_scan(source, force=False):
with job_lock: jobs[source]["done"] += 1
with job_lock: jobs[source]["state"] = "done"
except Exception as exc:
log.exception("%s-Scan fehlgeschlagen", source); jobs[source]["error"] = str(exc); jobs[source]["state"] = "error"
log.error("%s-Scan fehlgeschlagen: %s", source, error_summary(exc)); jobs[source]["error"] = str(exc); jobs[source]["state"] = "error"
executor.submit(work)
@app.route("/")
@@ -765,7 +772,7 @@ def index():
try:
series = sonarr_groups()
if any(row["pending"] for group in series for row in group["episodes"]): start_scan("sonarr")
except Exception as exc: log.exception("Sonarr nicht erreichbar"); error = f"{error + ' | ' if error else ''}Sonarr: {exc}"
except Exception as exc: log.error("Sonarr nicht erreichbar: %s", error_summary(exc)); error = f"{error + ' | ' if error else ''}Sonarr nicht erreichbar"
missing_german_movies = [row for row in rows if is_german_review_candidate(row) and not has_german_track(row)]
missing_german_series = []
for group in series:
@@ -821,24 +828,24 @@ def downloads():
try:
return jsonify(sab_downloads())
except Exception as exc:
log.exception("SABnzbd konnte nicht abgefragt werden")
return jsonify({"items": [], "queue_status": "Fehler", "speed": "", "paused": False, "active_count": 0, "history_count": 0, "remaining": "", "error": str(exc)}), 502
log.error("SABnzbd konnte nicht abgefragt werden: %s", error_summary(exc))
return jsonify({"items": [], "queue_status": "Nicht erreichbar", "speed": "", "paused": False, "active_count": 0, "history_count": 0, "remaining": "", "error": "SABnzbd ist momentan nicht erreichbar."}), 502
@app.get("/api/system")
def system():
try:
return jsonify(system_metrics())
except Exception as exc:
log.exception("Systemmetriken konnten nicht gelesen werden")
return jsonify({"error": str(exc), "cpu_percent": 0, "memory": {}, "disks": []}), 500
log.error("Systemmetriken konnten nicht gelesen werden: %s", error_summary(exc))
return jsonify({"error": "Systemmetriken momentan nicht verfügbar", "cpu_percent": 0, "memory": {}, "disks": []}), 500
@app.get("/api/datastore")
def datastore():
try:
return jsonify(datastore_snapshot())
except Exception as exc:
log.exception("Datastore konnte nicht abgefragt werden")
return jsonify({"storage": {"error": str(exc), "path": DATASTORE_PATH, "categories": []}, "raid": {"status": "failed", "label": "FAILED", "error": str(exc)}}), 500
log.error("Datastore konnte nicht abgefragt werden: %s", error_summary(exc))
return jsonify({"storage": {"error": "Datastore momentan nicht verfügbar", "path": DATASTORE_PATH, "categories": []}, "raid": {"status": "failed", "label": "FAILED", "error": "RAID-Status momentan nicht verfügbar"}}), 500
@app.get("/health")
def health(): return jsonify({"ok":True, "radarr_url":RADARR_URL, "sonarr_enabled":bool(SONARR_URL and SONARR_API_KEY), "sab_enabled":bool(SAB_URL and SAB_API_KEY)})