""" Run log. Python buffers stdout when it is not a terminal, so a background run that hung produced a zero-byte log and there was no way to see how far it had got — the only evidence left was a screenshot. Every run now tees to `Logs\\run_.log`, flushed per line, so a stuck or killed run still leaves a readable trail. """ import sys from datetime import datetime from .. import settings as cfg class _Tee: def __init__(self, stream, handle): self._stream = stream self._handle = handle def write(self, text): try: self._stream.write(text) self._stream.flush() except Exception: pass try: self._handle.write(text) self._handle.flush() # a killed run must still have its log except Exception: pass return len(text) def flush(self): for target in (self._stream, self._handle): try: target.flush() except Exception: pass def isatty(self): return getattr(self._stream, "isatty", lambda: False)() def _force_utf8_console() -> None: """ Make the console accept the characters this bot actually prints. Windows hands Python a cp1252 console, which cannot encode an em dash or an arrow — and nearly every log line here has one. The tee below swallows the resulting UnicodeEncodeError so a run never dies of it, which is right but means those lines were being **silently dropped from the console** while landing correctly in the log file. Watching a run and reading its log afterwards showed different things, which is a nasty way to debug. `errors="replace"` as well as the encoding: a console that still cannot represent a character should print a box, not raise. """ for stream in (sys.__stdout__, sys.__stderr__): try: stream.reconfigure(encoding="utf-8", errors="replace") except Exception: pass # not a real console, or too old to care def start() -> "object": """Begin teeing stdout/stderr. Returns the log path.""" _force_utf8_console() cfg.LOGS.mkdir(parents=True, exist_ok=True) path = cfg.LOGS / f"run_{datetime.now():%Y%m%d_%H%M%S}.log" handle = open(path, "a", encoding="utf-8", buffering=1) handle.write(f"\n{'=' * 70}\n{datetime.now():%Y-%m-%d %H:%M:%S} — run start\n" f"{'=' * 70}\n") sys.stdout = _Tee(sys.__stdout__, handle) sys.stderr = _Tee(sys.__stderr__, handle) return path