From 9b9310e8fd15a48f63a9cc96dd35190efe8a7267 Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Tue, 9 Jun 2026 15:44:42 +0200 Subject: [PATCH] notebook --- .claude/settings.local.json | 13 ++++- indexer/logger.py | 98 +++++++++++++++++++++++++++++++++++++ main.py | 22 +++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 59ad312..51496c0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -20,7 +20,18 @@ "Bash(\"C:\\\\Python\\\\python.exe\" -m pip list)", "Bash(findstr:*)", "Bash(ls:*)", - "Bash(C:Pythonpython.exe -m pip install pyzipper)" + "Bash(C:Pythonpython.exe -m pip install pyzipper)", + "mcp__knowledgebase__search", + "mcp__mcp-registry__list_connectors", + "mcp__knowledgebase__get_context", + "mcp__knowledgebase__get_recent", + "Bash(U:/janssen/.venv/Scripts/python.exe -c ' *)", + "Bash(U:/janssen/.venv/Scripts/python.exe -X utf8 -c ' *)", + "Bash(U:/janssen/.venv/Scripts/python.exe -X utf8 _store_fix_note.py)", + "Bash(curl -s \"http://192.168.1.76:3100/loki/api/v1/label/level/values\")", + "mcp__knowledgebase__stats", + "mcp__knowledgebase__store_memory", + "PowerShell(Test-NetConnection -ComputerName Reporter -Port 5985 -WarningAction SilentlyContinue)" ] } } diff --git a/indexer/logger.py b/indexer/logger.py index d24a3f5..63a1426 100644 --- a/indexer/logger.py +++ b/indexer/logger.py @@ -5,6 +5,97 @@ from logging.handlers import TimedRotatingFileHandler from indexer.config import LOG_LEVEL, LOG_DIR +# Loki label aplikace (stabilní, BEZ verze) — podle něj se filtruje v Grafaně: +# {app="dropbox_backup"} +CENTRAL_APP_NAME = "dropbox_backup" + +# Sdílené knihovny (central_logging klient) — na produkci leží v +# C:\Reporting\knihovny, v dev prostředí ve zdroji CentralLogging. +_LIB_DIRS = [ + r"C:\Reporting\knihovny", + r"U:\janssen\CentralLogging\client", +] + +# Token/gateway pro central logging držíme centrálně v /scripts na unraidu +# (\\tower\Scripts). Skript si je odsud načte do os.environ, takže není nutné +# nastavovat env proměnné na každém stroji ani mít token v repu/.env. +_CENTRAL_ENV_FILES = [ + r"\\tower\Scripts\central_log.env", + r"\\192.168.1.76\Scripts\central_log.env", +] + + +def _load_central_env() -> None: + """Načte CENTRAL_LOG_* z token souboru na unraidu (KEY=VALUE řádky) do + os.environ. Nepřepisuje už nastavené hodnoty (setdefault). Tiše ignoruje, + když share není dostupný — fallback řeší volající.""" + path = os.environ.get("CENTRAL_LOG_ENV_FILE") + candidates = [path] if path else _CENTRAL_ENV_FILES + for p in candidates: + if not p or not os.path.isfile(p): + continue + try: + with open(p, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + os.environ.setdefault(key.strip(), val.strip()) + return + except OSError: + continue + + +def _attach_central_handler(root: logging.Logger, fmt: logging.Formatter, level: int) -> bool: + """Připojí na root logger handler centrálního logování (Grafana Loki). + + Posílá KAŽDÝ log řádek (tedy i celou konzoli) do centrální gateway na + pozadí, neblokujícím způsobem se spool fallbackem. Vrací True při úspěchu. + Když klient/gateway nejsou dostupné (např. dev stroj), tiše degraduje na + file+console — záloha běží dál. + """ + _load_central_env() + for d in _LIB_DIRS: + if os.path.isdir(d) and d not in sys.path: + sys.path.insert(0, d) + try: + # importlib shim re-exportuje CentralLogHandler i _GatewaySender přes + # implementační modul; sender vytvoříme přímo, ať nepřepíšeme naše + # vlastní file+console handlery (central setup_logging si je čistí sám). + import central_logging # noqa: F401 (shim načte impl modul) + impl = sys.modules.get("central_logging_impl") or central_logging.setup_logging.__globals__ + if isinstance(impl, dict): + GatewaySender = impl["_GatewaySender"] + CentralLogHandler = impl["CentralLogHandler"] + else: + GatewaySender = impl._GatewaySender + CentralLogHandler = impl.CentralLogHandler + + from pathlib import Path + gw = os.environ.get("CENTRAL_LOG_GATEWAY", "http://192.168.1.76:8770") + tok = os.environ.get("CENTRAL_LOG_TOKEN", "change-this-shared-secret") + ev = os.environ.get("CENTRAL_LOG_ENV", "prod") + + sender = GatewaySender( + app_name=CENTRAL_APP_NAME, + gateway=gw, + token=tok, + env=ev, + spool_dir=Path(LOG_DIR) / "_log_spool", + ) + ch = CentralLogHandler(sender) + ch.setLevel(level) + ch.setFormatter(fmt) + root.addHandler(ch) + + import atexit + atexit.register(sender.flush_and_stop) + return True + except Exception as e: # noqa: BLE001 — logování nikdy nesmí shodit zálohu + print(f"[logger] central logging nedostupné, pokracuji file+console: {e}") + return False + def setup_logging() -> logging.Logger: os.makedirs(LOG_DIR, exist_ok=True) @@ -31,4 +122,11 @@ def setup_logging() -> logging.Logger: logging.root.addHandler(file_handler) logging.root.addHandler(console_handler) + # Central logging (Grafana Loki) — vedle file+console, posílá vše do gateway. + if _attach_central_handler(logging.root, fmt, level): + logging.getLogger("backup").info( + "central_logging napojeno (app=%s, gateway=%s)", + CENTRAL_APP_NAME, os.environ.get("CENTRAL_LOG_GATEWAY", "http://192.168.1.76:8770"), + ) + return logging.getLogger("backup") diff --git a/main.py b/main.py index de57d64..c32075e 100644 --- a/main.py +++ b/main.py @@ -14,6 +14,23 @@ from indexer.events import batch_log_events from indexer.backup import ensure_backed_up from indexer.hasher import is_cloud_placeholder, hydrate_file +# Strop pro výpis seznamu změn do logu (ochrana proti zaplavení při hromadných +# importech). Nad tento počet se v dané kategorii vypíše jen prvních N + zbytek. +MAX_LIST_LOG = 2000 + + +def _log_change_list(log, label: str, paths: list): + """Vypíše seznam souborů dané kategorie (ADDED/MODIFIED/DELETED) do logu. + Jde přes root logger, takže to skončí i v centrálním loggingu (Loki).""" + n = len(paths) + if n == 0: + return + log.info(f"--- {label} files ({n}) ---") + for p in paths[:MAX_LIST_LOG]: + log.info(f"[{label}] {p}") + if n > MAX_LIST_LOG: + log.info(f"[{label}] ... and {n - MAX_LIST_LOG} more") + def main(): log = setup_logging() @@ -170,6 +187,11 @@ def main(): if events: batch_log_events(cur, events) + # 5f) Explicitní seznam změn do logu (soubor + konzole + central logging) + _log_change_list(log, "ADDED", [nf["relative_path"] for nf in new_files]) + _log_change_list(log, "MODIFIED", sorted(modified_paths)) + _log_change_list(log, "DELETED", sorted(deleted_paths)) + # ── 6. Backup ── if files_to_backup and BACKUP_PATH: log.info(f"[6/7] Backing up {len(files_to_backup)} files...")