AHD-CMA / src /ahdcma /utils /logging.py
Akiyue's picture
Add files using upload-large-folder tool
f325414 verified
Raw
History Blame Contribute Delete
3.31 kB
"""Rich-formatted project logger with run-id binding.
Every entry-point script should call :func:`setup_logging` exactly once,
passing the run identifier produced by :func:`make_run_id`. The returned
logger writes to stderr via :class:`rich.logging.RichHandler` and to the
per-run log file under ``outputs/logs/{run_id}.log``.
"""
from __future__ import annotations
import logging
from datetime import UTC, datetime
from pathlib import Path
from rich.logging import RichHandler
def make_run_id(algo: str, task: str, seed: int, *, dim: int | None = None) -> str:
"""Return a canonical run identifier.
Format: ``{algo}_{task}[_d{dim}]_seed{N}_{YYYYMMDD-HHMMSS}``.
Parameters
----------
algo : str
Algorithm name (e.g., ``"ahdcma"``).
task : str
Task or benchmark name (e.g., ``"cifar100_vit"``).
seed : int
Run seed.
dim : int, optional
Dimensionality. When set, included in the run id so the same
``(algo, task, seed)`` tuple at different dimensions produces
distinct identifiers. Required for the CEC-2022 sweep where
each function is run at multiple dimensions.
"""
timestamp = datetime.now(tz=UTC).strftime("%Y%m%d-%H%M%S")
dim_part = f"_d{dim}" if dim is not None else ""
return f"{algo}_{task}{dim_part}_seed{seed}_{timestamp}"
class _RunIdFilter(logging.Filter):
"""Inject ``run_id`` into every record so the format string can use it."""
def __init__(self, run_id: str) -> None:
super().__init__()
self._run_id = run_id
def filter(self, record: logging.LogRecord) -> bool:
record.run_id = self._run_id
return True
def setup_logging(
run_id: str,
log_dir: str | Path = "outputs/logs",
*,
level: int = logging.INFO,
verbose: bool = False,
) -> logging.Logger:
"""Configure the root ``ahdcma`` logger and return it.
Parameters
----------
run_id : str
Run identifier; the file handler writes to ``{log_dir}/{run_id}.log``.
log_dir : str or Path
Directory for the per-run log file. Created if missing.
level : int
Default logging level for the console handler.
verbose : bool
If ``True``, drop the console level to ``DEBUG``.
Returns
-------
logging.Logger
The configured ``ahdcma`` logger. Calling this twice with the same
``run_id`` is idempotent.
"""
log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger("ahdcma")
logger.setLevel(logging.DEBUG)
logger.handlers.clear()
fmt = "[%(asctime)s] [%(levelname)s] [run_id=%(run_id)s] %(message)s"
datefmt = "%Y-%m-%d %H:%M:%S"
console = RichHandler(rich_tracebacks=True, show_time=False, show_path=False)
console.setLevel(logging.DEBUG if verbose else level)
console.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
file_handler = logging.FileHandler(log_path / f"{run_id}.log")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
run_filter = _RunIdFilter(run_id)
for handler in (console, file_handler):
handler.addFilter(run_filter)
logger.addHandler(handler)
logger.propagate = False
return logger