diff --git a/launch/utilities/logger.py b/launch/utilities/logger.py index 8bb9590..5c35882 100644 --- a/launch/utilities/logger.py +++ b/launch/utilities/logger.py @@ -9,6 +9,25 @@ import io, sys from rich.console import Console +# A fresh TextIOWrapper around sys.stdout.buffer per setup_logger call closes +# the underlying buffer (= process stdout) once the wrapper is garbage +# collected after clean_logger removes the handler. The next setup_logger call +# in the same process (e.g. the organize stage after the setup stage) then +# fails with "ValueError: I/O operation on closed file". Share a single, +# never-collected console instead. +_shared_console: Console | None = None + + +def _get_shared_console() -> Console: + global _shared_console + if _shared_console is None: + # Wrap stdout in a UTF-8 TextIOWrapper; replace any bad chars defensively + utf8_stdout = io.TextIOWrapper( + sys.stdout.buffer, encoding="utf-8", errors="replace" + ) + _shared_console = Console(file=utf8_stdout, soft_wrap=True) + return _shared_console + def setup_logger(instance_id: str, log_file: Path | list[Path], printing: bool = True) -> logging.Logger: """ @@ -43,10 +62,7 @@ def setup_logger(instance_id: str, log_file: Path | list[Path], printing: bool = # logger.addHandler(ch) # add rich handler if printing: - # Wrap stdout in a UTF-8 TextIOWrapper; replace any bad chars defensively - utf8_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") - console = Console(file=utf8_stdout, soft_wrap=True) - rh = RichHandler(console=console, rich_tracebacks=True, show_path=False) + rh = RichHandler(console=_get_shared_console(), rich_tracebacks=True, show_path=False) rh.setLevel(logging.INFO) logger.addHandler(rh) return logger