import logging import os import sys 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) level = getattr(logging, LOG_LEVEL.upper(), logging.INFO) fmt = logging.Formatter( "%(asctime)s [%(levelname)-8s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) file_handler = TimedRotatingFileHandler( os.path.join(LOG_DIR, "backup.log"), when="midnight", backupCount=90, encoding="utf-8", ) file_handler.setFormatter(fmt) console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(fmt) logging.root.setLevel(level) logging.root.handlers.clear() 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")