"""Shared UTF-8 logging helpers for OCR scripts.""" from __future__ import annotations import logging import re import sys from datetime import datetime from pathlib import Path def safe_log_stem(value: str) -> str: cleaned = re.sub(r"[^\w.-]+", "_", value, flags=re.UNICODE).strip("._") return cleaned or "ocr" def default_log_path( project_root: Path, category: str, stem: str, *, device: str | None = None, ) -> Path: timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") suffix = f"-{safe_log_stem(device)}" if device else "" filename = f"{safe_log_stem(stem)}{suffix}-{timestamp}.log" return project_root / "logs" / safe_log_stem(category) / filename def setup_run_logger( name: str, log_file: Path, *, verbose: bool = False, console: bool = True, ) -> logging.Logger: """Create an isolated logger that writes UTF-8 text and optional console output.""" log_file = log_file.expanduser().resolve() log_file.parent.mkdir(parents=True, exist_ok=True) logger = logging.getLogger(name) logger.setLevel(logging.DEBUG if verbose else logging.INFO) logger.propagate = False for handler in logger.handlers[:]: handler.close() logger.removeHandler(handler) formatter = logging.Formatter( fmt="%(asctime)s | %(levelname)-8s | pid=%(process)d | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) file_handler = logging.FileHandler(log_file, encoding="utf-8") file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) logger.addHandler(file_handler) if console: console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.DEBUG if verbose else logging.INFO) console_handler.setFormatter(formatter) logger.addHandler(console_handler) logger.info("LOG_INITIALIZED file=%s", log_file) return logger def close_logger(logger: logging.Logger) -> None: for handler in logger.handlers[:]: try: handler.flush() handler.close() finally: logger.removeHandler(handler)