#!/usr/bin/env python3 """Raccoglie gli ultimi eventi dei log critici e produce un report giornaliero.""" from __future__ import annotations import argparse import datetime as dt from collections import deque from pathlib import Path from typing import Iterable BASE_DIR = Path(__file__).resolve().parent REPORTS_DIR = BASE_DIR / "reports" LOG_SOURCES = [ ("backup_completo", Path("/mnt/ssd/adrian/scripts/logs/backup_completo.log")), ("pcloud_mirror", Path("/mnt/ssd/adrian/scripts/logs/pcloud_mirror.log")), ("snapshot_local", Path("/mnt/ssd/adrian/config-backups/snapshot.log")), ("snapshot_avamposto", Path("/mnt/ssd/adrian/config-backups/avamposto_snapshot.log")), ] # Parole chiave che indicano possibili problemi KEYWORDS = ( "fatal", "error", "failed", "fallito", "permission denied", "timeout", "unable", ) def _ensure_symlink(name: str, path: Path) -> None: """Crea/aggiusta un symlink nel logbook per facile consultazione.""" link_path = BASE_DIR / f"{name}.log" try: if link_path.is_symlink() or link_path.exists(): if link_path.resolve() == path: return link_path.unlink() link_path.symlink_to(path) except OSError: # se non riusciamo a creare il symlink, proseguiamo comunque pass def _tail(path: Path, max_lines: int = 120) -> list[str]: """Ritorna le ultime max_lines dal file.""" try: with path.open("r", encoding="utf-8", errors="ignore") as handle: dq: deque[str] = deque(handle, max_lines) return list(dq) except OSError: return [] def _scan_for_issues(lines: Iterable[str]) -> list[str]: """Ritorna le righe che contengono parole chiave critiche.""" issues = [] for line in lines: lower = line.lower() if "completed without errors" in lower or "no errors were found" in lower: continue if any(keyword in lower for keyword in KEYWORDS): issues.append(line.rstrip()) return issues def build_report(now: dt.datetime, max_lines: int) -> tuple[str, bool]: """Crea il report testuale e indica se sono emerse anomalie.""" REPORTS_DIR.mkdir(parents=True, exist_ok=True) report_lines = [ f"# Logbook report – {now.isoformat(timespec='seconds')}", "", ] global_issues: list[str] = [] for name, path in LOG_SOURCES: _ensure_symlink(name, path) report_lines.append(f"## {name}") report_lines.append(f"Percorso: {path}") if not path.exists(): report_lines.append("⚠️ Log mancante.") report_lines.append("") global_issues.append(f"{name}: log assente") continue tail_lines = _tail(path, max_lines=max_lines) if not tail_lines: report_lines.append("ℹ️ Nessun contenuto (file vuoto?).") report_lines.append("") continue issues = _scan_for_issues(tail_lines) if issues: report_lines.append("⚠️ Anomalie rilevate nelle ultime righe:") report_lines.extend(f" {line}" for line in issues) report_lines.append("") global_issues.append(f"{name}: {len(issues)} segnalazioni") else: report_lines.append("✅ Nessuna anomalia rilevata nelle ultime righe.") report_lines.append("") report_lines.append("```\n" + "\n".join(tail_lines) + "\n```") report_lines.append("") header = ( "⚠️ Problemi individuati: " + ", ".join(global_issues) if global_issues else "✅ Tutti i log analizzati risultano OK." ) report_lines.insert(2, header) report_text = "\n".join(report_lines).strip() + "\n" return report_text, bool(global_issues) def write_report(content: str, now: dt.datetime) -> Path: filename = f"report_{now.strftime('%Y%m%d')}.txt" report_path = REPORTS_DIR / filename report_path.write_text(content, encoding="utf-8") latest = REPORTS_DIR / "latest.txt" latest.write_text(content, encoding="utf-8") return report_path def main() -> None: parser = argparse.ArgumentParser( description="Genera un riepilogo dai log principali della Flotta." ) parser.add_argument( "--lines", type=int, default=120, help="Numero di righe da includere per ciascun log (default: 120).", ) args = parser.parse_args() now = dt.datetime.now() report_text, has_issues = build_report(now, max_lines=args.lines) path = write_report(report_text, now) print(report_text) if has_issues: print(f"⚠️ Anomalie rilevate. Vedi {path}") else: print(f"Report salvato in {path}") if __name__ == "__main__": main()