"""Sidecar entrypoint: the tick loop with kill-switch + per-tick never-raise (D8). Run as ``python -m watchdog`` (the container ``ENTRYPOINT``). The loop: * honours ``WATCHDOG_ENABLED=false`` -> stays INERT (idle-loops with a log line, does NOT ``exit``, so ``restart: unless-stopped`` does not spin a restart loop); * wraps every tick in an outer ``try/except`` so a tick error logs and the daemon survives (per-tick never-raise); * logs start / each tick so the container logs prove the sidecar is alive and why an alert did (not) fire (NFR-7). """ from __future__ import annotations import logging import time from .config import Config from .core import Watchdog logger = logging.getLogger("watchdog") def _setup_logging() -> None: logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) def run(cfg: Config | None = None, max_ticks: int | None = None) -> None: """Run the tick loop. ``max_ticks`` bounds the loop for tests (``None`` = forever).""" cfg = cfg or Config.from_env() if not cfg.enabled: logger.info("watchdog: WATCHDOG_ENABLED=false -> inert (idle, no ticks)") # Idle, not exit: keep the container up so restart-policy does not flap. ticks = 0 while max_ticks is None or ticks < max_ticks: time.sleep(cfg.interval_s) ticks += 1 return logger.info( "watchdog started (interval=%ss, metrics=%s, containers=%s, deps=%s, " "mem_pct=%s, disk_crit=%s)", cfg.interval_s, cfg.metrics_url, cfg.containers, list(cfg.deps), cfg.mem_pct, cfg.disk_crit_enabled, ) dog = Watchdog(cfg) ticks = 0 while max_ticks is None or ticks < max_ticks: try: dispatched = dog.tick() fired = [ (a, getattr(s, "key", None)) for a, s in dispatched if a != "none" ] logger.info("watchdog tick ok (fired=%s)", fired) except Exception as e: # noqa: BLE001 - per-tick outer never-raise (D8) logger.error("watchdog tick error: %s", e) ticks += 1 if max_ticks is not None and ticks >= max_ticks: break time.sleep(cfg.interval_s) def main() -> None: _setup_logging() run() if __name__ == "__main__": main()