wip
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""Central logging configuration for the Seed project.
|
||||
|
||||
Usage anywhere in the codebase:
|
||||
from logger import get_logger
|
||||
log = get_logger(__name__)
|
||||
log.info("message")
|
||||
log.warning("watch out")
|
||||
log.error("something broke")
|
||||
|
||||
Environment variables:
|
||||
LOG_LEVEL — DEBUG | INFO | WARNING | ERROR | CRITICAL (default: INFO)
|
||||
LOG_FILE — path to write a rotating log file (optional)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from typing import Final
|
||||
|
||||
_LEVEL_MAP: Final = {
|
||||
"DEBUG": logging.DEBUG,
|
||||
"INFO": logging.INFO,
|
||||
"WARNING": logging.WARNING,
|
||||
"ERROR": logging.ERROR,
|
||||
"CRITICAL": logging.CRITICAL,
|
||||
}
|
||||
|
||||
_LOG_LEVEL: int = _LEVEL_MAP.get(os.getenv("LOG_LEVEL", "INFO").upper(), logging.INFO)
|
||||
_LOG_FILE: str | None = os.getenv("LOG_FILE")
|
||||
|
||||
# ── ANSI colour codes (console only) ─────────────────────────────────────────
|
||||
_RESET = "\033[0m"
|
||||
_BOLD = "\033[1m"
|
||||
_GREY = "\033[90m"
|
||||
_CYAN = "\033[96m"
|
||||
_YELLOW = "\033[93m"
|
||||
_RED = "\033[91m"
|
||||
_BRED = "\033[1;91m"
|
||||
|
||||
_LEVEL_COLOURS: Final[dict[int, str]] = {
|
||||
logging.DEBUG: _GREY,
|
||||
logging.INFO: _CYAN,
|
||||
logging.WARNING: _YELLOW,
|
||||
logging.ERROR: _RED,
|
||||
logging.CRITICAL: _BRED,
|
||||
}
|
||||
|
||||
|
||||
class _ColourFormatter(logging.Formatter):
|
||||
_FMT = "{colour}{level:<8}{reset} {grey}{asctime} {name}{reset} {msg}"
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
colour = _LEVEL_COLOURS.get(record.levelno, "")
|
||||
level = record.levelname
|
||||
grey = _GREY if sys.stderr.isatty() else ""
|
||||
reset = _RESET if sys.stderr.isatty() else ""
|
||||
col = colour if sys.stderr.isatty() else ""
|
||||
|
||||
self.datefmt = "%H:%M:%S"
|
||||
base = super().format(record)
|
||||
return self._FMT.format(
|
||||
colour=col, level=level, reset=reset,
|
||||
grey=grey, asctime=self.formatTime(record, self.datefmt),
|
||||
name=record.name, msg=record.getMessage(),
|
||||
) + (f"\n{record.exc_text}" if record.exc_info and self.formatException(record.exc_info) else "")
|
||||
|
||||
|
||||
_FILE_FMT = logging.Formatter(
|
||||
fmt="%(asctime)s %(levelname)-8s %(name)s %(message)s",
|
||||
datefmt="%Y-%m-%dT%H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def _build_root_logger() -> logging.Logger:
|
||||
root = logging.getLogger("seed")
|
||||
root.setLevel(_LOG_LEVEL)
|
||||
|
||||
if root.handlers:
|
||||
return root # already configured (e.g. imported twice)
|
||||
|
||||
# Console handler
|
||||
console = logging.StreamHandler(sys.stderr)
|
||||
console.setLevel(_LOG_LEVEL)
|
||||
console.setFormatter(_ColourFormatter())
|
||||
root.addHandler(console)
|
||||
|
||||
# Optional rotating file handler
|
||||
if _LOG_FILE:
|
||||
fh = logging.handlers.RotatingFileHandler(
|
||||
_LOG_FILE, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||
)
|
||||
fh.setLevel(_LOG_LEVEL)
|
||||
fh.setFormatter(_FILE_FMT)
|
||||
root.addHandler(fh)
|
||||
|
||||
root.propagate = False
|
||||
return root
|
||||
|
||||
|
||||
_root = _build_root_logger()
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""Return a child logger namespaced under 'seed'."""
|
||||
if name.startswith("seed.") or name == "seed":
|
||||
return logging.getLogger(name)
|
||||
return logging.getLogger(f"seed.{name}")
|
||||
Reference in New Issue
Block a user