diff --git a/app.py b/app.py index b5efe3e..81e7a01 100644 --- a/app.py +++ b/app.py @@ -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)})