# -*- coding: utf-8 -*- from __future__ import annotations import logging import sys from contextlib import contextmanager from datetime import datetime from pathlib import Path from typing import Iterator, TextIO class TeeStream: def __init__(self, *streams: TextIO) -> None: self._streams = streams def write(self, data: str) -> int: for stream in self._streams: stream.write(data) return len(data) def flush(self) -> None: for stream in self._streams: stream.flush() def isatty(self) -> bool: return False def fileno(self) -> int: return self._streams[0].fileno() def build_log_path(log_dir: Path, prefix: str, tag: str = "") -> Path: suffix = f"_{tag}" if tag else "" stamp = datetime.now().strftime("%Y%m%d_%H%M%S") return log_dir / f"{prefix}{suffix}_{stamp}.log" @contextmanager def configure_logging( name: str, log_file: Path | None, *, level: str = "INFO", console: bool = True, tee_std: bool = True, ) -> Iterator[logging.Logger]: logger = logging.getLogger(name) logger.handlers.clear() logger.setLevel(getattr(logging, level.upper(), logging.INFO)) logger.propagate = False formatter = logging.Formatter( "%(asctime)s [%(levelname)s] %(name)s: %(message)s", "%Y-%m-%d %H:%M:%S", ) original_stdout = sys.stdout original_stderr = sys.stderr log_fp: TextIO | None = None try: if log_file: log_file.parent.mkdir(parents=True, exist_ok=True) log_fp = open(log_file, "a", encoding="utf-8", buffering=1) if tee_std: if console: sys.stdout = TeeStream(original_stdout, log_fp) sys.stderr = TeeStream(original_stderr, log_fp) else: sys.stdout = log_fp sys.stderr = log_fp file_handler = logging.StreamHandler(log_fp) file_handler.setFormatter(formatter) logger.addHandler(file_handler) if console: console_handler = logging.StreamHandler(original_stdout) console_handler.setFormatter(formatter) logger.addHandler(console_handler) yield logger finally: for handler in list(logger.handlers): handler.flush() handler.close() logger.removeHandler(handler) if log_fp: log_fp.flush() log_fp.close() sys.stdout = original_stdout sys.stderr = original_stderr