Spaces:
Running
Running
| """ | |
| utils/logger.py β Centralised logging configuration. | |
| Writes simultaneously to stdout (coloured) and a rotating file log. | |
| """ | |
| import logging | |
| import os | |
| import sys | |
| from datetime import datetime | |
| from logging.handlers import RotatingFileHandler | |
| import config | |
| class _ColourFormatter(logging.Formatter): | |
| """ANSI colour codes for terminal output.""" | |
| GREY = "\033[38;5;240m" | |
| CYAN = "\033[36m" | |
| YELLOW = "\033[33m" | |
| RED = "\033[31m" | |
| BOLD_R = "\033[1;31m" | |
| RESET = "\033[0m" | |
| LEVEL_COLOURS = { | |
| logging.DEBUG : GREY, | |
| logging.INFO : CYAN, | |
| logging.WARNING : YELLOW, | |
| logging.ERROR : RED, | |
| logging.CRITICAL: BOLD_R, | |
| } | |
| def format(self, record: logging.LogRecord) -> str: | |
| colour = self.LEVEL_COLOURS.get(record.levelno, self.RESET) | |
| fmt = ( | |
| f"{self.GREY}%(asctime)s{self.RESET} " | |
| f"{colour}%(levelname)-8s{self.RESET} " | |
| f"%(name)-20s %(message)s" | |
| ) | |
| formatter = logging.Formatter(fmt, datefmt="%H:%M:%S") | |
| return formatter.format(record) | |
| def get_logger(name: str) -> logging.Logger: | |
| """ | |
| Return a configured logger for *name*. | |
| All loggers share the same handlers so duplicate lines never appear. | |
| Call once per module: logger = get_logger(__name__) | |
| """ | |
| logger = logging.getLogger(name) | |
| # Avoid adding handlers more than once (module re-imports) | |
| if logger.handlers: | |
| return logger | |
| level = getattr(logging, config.LOG_LEVEL.upper(), logging.INFO) | |
| logger.setLevel(level) | |
| # ββ Console handler ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ch = logging.StreamHandler(sys.stdout) | |
| ch.setLevel(level) | |
| ch.setFormatter(_ColourFormatter()) | |
| logger.addHandler(ch) | |
| # ββ File handler βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if config.LOG_TO_FILE: | |
| log_filename = os.path.join( | |
| config.LOGS_DIR, | |
| f"scraper_{datetime.now().strftime('%Y%m%d')}.log", | |
| ) | |
| fh = RotatingFileHandler( | |
| log_filename, | |
| maxBytes=5 * 1024 * 1024, # 5 MB | |
| backupCount=7, | |
| encoding="utf-8", | |
| ) | |
| fh.setLevel(level) | |
| plain_fmt = logging.Formatter( | |
| "%(asctime)s %(levelname)-8s %(name)-20s %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S", | |
| ) | |
| fh.setFormatter(plain_fmt) | |
| logger.addHandler(fh) | |
| # Prevent propagation to root logger (avoids double-printing) | |
| logger.propagate = False | |
| return logger | |