| """ |
| Centralized logging setup for the project. |
| |
| Usage β call once at the very start of any entry point: |
| |
| from utils.logger import setup_logging |
| setup_logging() |
| |
| Then in any module just use: |
| |
| import logging |
| logger = logging.getLogger(__name__) |
| logger.info("...") |
| """ |
|
|
| import logging |
| import sys |
| from pathlib import Path |
| from datetime import datetime |
|
|
| |
| LOG_DIR = Path("logs") |
| LOG_FMT = "%(asctime)s %(levelname)-8s [%(name)s] %(message)s" |
| DATE_FMT = "%Y-%m-%d %H:%M:%S" |
|
|
|
|
| def setup_logging( |
| level: int = logging.INFO, |
| log_dir: Path = LOG_DIR, |
| filename: str | None = None, |
| ) -> None: |
| """ |
| Configure root logger to stream to both terminal and a rotating log file. |
| |
| Args: |
| level: logging level (default: INFO) |
| log_dir: directory where log files are stored (default: logs/) |
| filename: log file name (default: YYYY-MM-DD_HH-MM-SS.log) |
| """ |
| log_dir.mkdir(parents=True, exist_ok=True) |
|
|
| if filename is None: |
| filename = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".log" |
|
|
| log_path = log_dir / filename |
|
|
| formatter = logging.Formatter(fmt=LOG_FMT, datefmt=DATE_FMT) |
|
|
| |
| stream_handler = logging.StreamHandler(sys.stdout) |
| stream_handler.setFormatter(formatter) |
| stream_handler.setLevel(level) |
|
|
| |
| file_handler = logging.FileHandler(log_path, encoding="utf-8") |
| file_handler.setFormatter(formatter) |
| file_handler.setLevel(level) |
|
|
| |
| root = logging.getLogger() |
| root.setLevel(level) |
|
|
| |
| if not root.handlers: |
| root.addHandler(stream_handler) |
| root.addHandler(file_handler) |
| else: |
| root.handlers.clear() |
| root.addHandler(stream_handler) |
| root.addHandler(file_handler) |
|
|
| logging.info("Logging initialised β %s", log_path) |