92 lines
2.5 KiB
Python
92 lines
2.5 KiB
Python
# -*- 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
|