fix: keep dashboard logging inside curses UI
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
|
import logging
|
||||||
|
from collections import deque
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -35,6 +37,10 @@ class ReactorDashboard:
|
|||||||
self.pending_command: Optional[ReactorCommand] = None
|
self.pending_command: Optional[ReactorCommand] = None
|
||||||
self.sim: Optional[ReactorSimulation] = None
|
self.sim: Optional[ReactorSimulation] = None
|
||||||
self.quit_requested = False
|
self.quit_requested = False
|
||||||
|
self.log_buffer: deque[str] = deque(maxlen=4)
|
||||||
|
self._log_handler: Optional[logging.Handler] = None
|
||||||
|
self._previous_handlers: list[logging.Handler] = []
|
||||||
|
self._logger = logging.getLogger("reactor_sim")
|
||||||
self.keys = [
|
self.keys = [
|
||||||
DashboardKey("q", "Quit & save"),
|
DashboardKey("q", "Quit & save"),
|
||||||
DashboardKey("space", "SCRAM"),
|
DashboardKey("space", "SCRAM"),
|
||||||
@@ -61,6 +67,7 @@ class ReactorDashboard:
|
|||||||
curses.init_pair(3, curses.COLOR_GREEN, -1)
|
curses.init_pair(3, curses.COLOR_GREEN, -1)
|
||||||
curses.init_pair(4, curses.COLOR_RED, -1)
|
curses.init_pair(4, curses.COLOR_RED, -1)
|
||||||
stdscr.nodelay(True)
|
stdscr.nodelay(True)
|
||||||
|
self._install_log_capture()
|
||||||
self.sim = ReactorSimulation(
|
self.sim = ReactorSimulation(
|
||||||
self.reactor,
|
self.reactor,
|
||||||
timestep=self.timestep,
|
timestep=self.timestep,
|
||||||
@@ -79,6 +86,7 @@ class ReactorDashboard:
|
|||||||
finally:
|
finally:
|
||||||
if self.save_path and self.sim and self.sim.last_state:
|
if self.save_path and self.sim and self.sim.last_state:
|
||||||
self.reactor.save_state(self.save_path, self.sim.last_state)
|
self.reactor.save_state(self.save_path, self.sim.last_state)
|
||||||
|
self._restore_logging()
|
||||||
|
|
||||||
def _handle_input(self, stdscr: "curses._CursesWindow") -> None:
|
def _handle_input(self, stdscr: "curses._CursesWindow") -> None:
|
||||||
while True:
|
while True:
|
||||||
@@ -265,7 +273,13 @@ class ReactorDashboard:
|
|||||||
f"Failures: {', '.join(self.reactor.health_monitor.failure_log)}",
|
f"Failures: {', '.join(self.reactor.health_monitor.failure_log)}",
|
||||||
curses.color_pair(4) | curses.A_BOLD,
|
curses.color_pair(4) | curses.A_BOLD,
|
||||||
)
|
)
|
||||||
win.addstr(4, 1, "Press 'q' to exit and persist the current snapshot.", curses.color_pair(2))
|
log_y = 3
|
||||||
|
for record in list(self.log_buffer):
|
||||||
|
if log_y >= win.getmaxyx()[0] - 1:
|
||||||
|
break
|
||||||
|
win.addstr(log_y, 1, record[: win.getmaxyx()[1] - 2], curses.color_pair(2))
|
||||||
|
log_y += 1
|
||||||
|
win.addstr(log_y, 1, "Press 'q' to exit and persist the current snapshot.", curses.color_pair(2))
|
||||||
|
|
||||||
def _draw_section(
|
def _draw_section(
|
||||||
self,
|
self,
|
||||||
@@ -320,3 +334,34 @@ class ReactorDashboard:
|
|||||||
def _clamped_rod(self, delta: float) -> float:
|
def _clamped_rod(self, delta: float) -> float:
|
||||||
new_fraction = self.reactor.control.rod_fraction + delta
|
new_fraction = self.reactor.control.rod_fraction + delta
|
||||||
return max(0.0, min(0.95, new_fraction))
|
return max(0.0, min(0.95, new_fraction))
|
||||||
|
|
||||||
|
def _install_log_capture(self) -> None:
|
||||||
|
if self._log_handler:
|
||||||
|
return
|
||||||
|
self._previous_handlers = list(self._logger.handlers)
|
||||||
|
for handler in self._previous_handlers:
|
||||||
|
self._logger.removeHandler(handler)
|
||||||
|
handler = _DashboardLogHandler(self.log_buffer)
|
||||||
|
handler.setFormatter(logging.Formatter("%(levelname)s | %(message)s"))
|
||||||
|
self._logger.addHandler(handler)
|
||||||
|
self._logger.propagate = False
|
||||||
|
self._log_handler = handler
|
||||||
|
|
||||||
|
def _restore_logging(self) -> None:
|
||||||
|
if not self._log_handler:
|
||||||
|
return
|
||||||
|
self._logger.removeHandler(self._log_handler)
|
||||||
|
for handler in self._previous_handlers:
|
||||||
|
self._logger.addHandler(handler)
|
||||||
|
self._log_handler = None
|
||||||
|
self._previous_handlers = []
|
||||||
|
|
||||||
|
|
||||||
|
class _DashboardLogHandler(logging.Handler):
|
||||||
|
def __init__(self, buffer: deque[str]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.buffer = buffer
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord) -> None:
|
||||||
|
msg = self.format(record)
|
||||||
|
self.buffer.append(msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user