feat: add file-based logging with hourly rotation and configurable retention period
Add LOG_FILE and LOG_RETENTION_HOURS environment variables to .env.example with defaults /data/media-max.log and 6 hours. Install TimedRotatingFileHandler with hourly rotation, 1 backup, UTF-8 encoding, and UTC timestamps. Add OSError exception handling with warning log when file logging unavailable. Add warn_on_nonzero parameter to _read_command() with default True and disable for smartctl -H calls to suppress expected
This commit is contained in:
@@ -38,4 +38,6 @@ STORAGE_CACHE_SECONDS=900
|
||||
# Optional: SMART-Abfragen aktivieren (benötigt Zugriff auf Host-Devices)
|
||||
SMART_ENABLED=false
|
||||
HOST_SYS_PATH=/host-sys
|
||||
LOG_FILE=/data/media-max.log
|
||||
LOG_RETENTION_HOURS=6
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
@@ -44,9 +45,17 @@ HOST_DEVICE_PATH = os.environ.get("HOST_DEVICE_PATH", "/host-dev")
|
||||
HOST_SYS_PATH = os.environ.get("HOST_SYS_PATH", "/host-sys")
|
||||
STORAGE_CACHE_SECONDS = int(os.environ.get("STORAGE_CACHE_SECONDS", "900"))
|
||||
SMART_ENABLED = os.environ.get("SMART_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
|
||||
LOG_FILE = os.environ.get("LOG_FILE", "/data/media-max.log")
|
||||
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")
|
||||
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"))
|
||||
log.addHandler(file_handler)
|
||||
except OSError as exc:
|
||||
log.warning("Datei-Logging nicht verfügbar (%s): %s", LOG_FILE, exc)
|
||||
app = Flask(__name__)
|
||||
executor = ThreadPoolExecutor(max_workers=int(os.environ.get("SCAN_WORKERS", "2")))
|
||||
scan_executor = ThreadPoolExecutor(max_workers=int(os.environ.get("SCAN_WORKERS", "4")))
|
||||
@@ -233,12 +242,12 @@ def scan_datastore_storage():
|
||||
"free": format_bytes(usage.free), "percent": round(usage.used / usage.total * 100, 1) if usage.total else 0,
|
||||
"categories": values, "scanned_at": time.time()}
|
||||
|
||||
def _read_command(command, timeout=4):
|
||||
def _read_command(command, timeout=4, warn_on_nonzero=True):
|
||||
command_text = " ".join(str(part) for part in command)
|
||||
log.debug("Systembefehl gestartet: %s", command_text)
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False)
|
||||
if result.returncode != 0:
|
||||
if result.returncode != 0 and warn_on_nonzero:
|
||||
log.warning("Systembefehl beendet mit Code %s: %s%s", result.returncode, command_text, f" · {result.stderr.strip()[:180]}" if result.stderr.strip() else "")
|
||||
return result.stdout, result.stderr
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
@@ -386,9 +395,9 @@ def raid_status():
|
||||
hardware_log_once.add(serial_log_key)
|
||||
if SMART_ENABLED and shutil.which("smartctl"):
|
||||
smart_device = disk["device"].replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1)
|
||||
smart_output, smart_error = _read_command(["smartctl", "-H", smart_device], timeout=6)
|
||||
smart_output, smart_error = _read_command(["smartctl", "-H", smart_device], timeout=6, warn_on_nonzero=False)
|
||||
if not re.search(r"(?:overall-health|Health Status|SMART support is)", smart_output, re.IGNORECASE):
|
||||
sat_output, sat_error = _read_command(["smartctl", "-H", "-d", "sat", smart_device], timeout=6)
|
||||
sat_output, sat_error = _read_command(["smartctl", "-H", "-d", "sat", smart_device], timeout=6, warn_on_nonzero=False)
|
||||
if sat_output:
|
||||
smart_output, smart_error = sat_output, sat_error
|
||||
health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE)
|
||||
|
||||
Reference in New Issue
Block a user