""" bridge_logging.py - browser log stream plumbing for the bridge. Holds the ring buffer + SSE queues that feed the Web UI's live log view, the logging.Handler that populates them, and the verbose-HTTP-log toggle. Extracted from kobrax_moonraker_bridge.py; the buffer/queues are re-imported there so the log-stream/download endpoints keep working against the same shared objects. ──────────────────────────────────────────────────────────────────────────── Copyright (C) 2026 viewit (KX-Bridge contributors) Licensed under GPLv3 — see LICENSE in the project root. See NOTICE.md. """ import collections as _collections import logging def _set_verbose_http_log(enabled: bool): logging.getLogger("aiohttp.access").setLevel(logging.INFO if enabled else logging.WARNING) # Ring buffer for the browser log stream + the open SSE consumer queues. # These are shared mutable objects: kobrax_moonraker_bridge re-imports the # same buffer/list so its log-stream/download handlers append/read the very # same instances this handler writes to. _log_buffer: "_collections.deque[dict]" = _collections.deque(maxlen=500) _log_sse_queues: list = [] class _BrowserLogHandler(logging.Handler): """Sends log records to the ring buffer and all open SSE queues.""" _fmt = logging.Formatter(datefmt="%H:%M:%S") def emit(self, record: logging.LogRecord): msg = record.getMessage() # Pass exceptions with traceback through to the browser (otherwise the # user only sees "Error: X" without context). if record.exc_info: try: msg += "\n" + self._fmt.formatException(record.exc_info) except Exception: pass entry = { "ts": self._fmt.formatTime(record, "%H:%M:%S"), "lvl": record.levelname, "name": record.name, "msg": msg, } _log_buffer.append(entry) for q in list(_log_sse_queues): try: q.put_nowait(entry) except Exception: pass _browser_handler = _BrowserLogHandler() logging.getLogger().addHandler(_browser_handler)