"""Unified logging with RotatingFileHandler for all Sanad modules.""" from __future__ import annotations import logging import sys from logging.handlers import RotatingFileHandler from pathlib import Path from Project.Sanad.config import LOGS_DIR _MAX_BYTES = 10 * 1024 * 1024 # 10 MB _BACKUP_COUNT = 3 _FMT = "%(asctime)s [%(name)s] %(levelname)s %(message)s" _formatter = logging.Formatter(_FMT) # Callback for the WebSocket log stream — set by log_stream.py at import time. _ws_push_fn = None def set_ws_push(fn): """Register the push function from dashboard.websockets.log_stream.""" global _ws_push_fn _ws_push_fn = fn class _WSHandler(logging.Handler): """Forwards every log record to the WebSocket log stream.""" def emit(self, record: logging.LogRecord): if _ws_push_fn is not None: try: _ws_push_fn(self.format(record)) except Exception: pass def get_logger(name: str, *, to_console: bool = True) -> logging.Logger: """Return a module-level logger that writes to logs/.log (rotating).""" logger = logging.getLogger(f"sanad.{name}") if logger.handlers: return logger logger.setLevel(logging.DEBUG) logger.propagate = False LOGS_DIR.mkdir(parents=True, exist_ok=True) fh = RotatingFileHandler( LOGS_DIR / f"{name}.log", maxBytes=_MAX_BYTES, backupCount=_BACKUP_COUNT ) fh.setFormatter(_formatter) fh.setLevel(logging.DEBUG) logger.addHandler(fh) if to_console: sh = logging.StreamHandler(sys.stdout) sh.setFormatter(_formatter) sh.setLevel(logging.INFO) logger.addHandler(sh) # WebSocket stream handler wsh = _WSHandler() wsh.setFormatter(_formatter) wsh.setLevel(logging.INFO) logger.addHandler(wsh) return logger