Source code for utils.logger

"""Application logging setup.

Configures a hierarchical logger under the ``differential_lab`` namespace.
Log level, file output, and console output are controlled via environment
variables (LOG_LEVEL, LOG_FILE, LOG_CONSOLE).
"""

import logging
import sys

_CONFIGURED = False
_LOGGER_NS = "differential_lab"


def _setup_root_logger() -> None:
    """Configure the root ``differential_lab`` logger once from env settings.

    Idempotent: subsequent calls have no effect after the first configuration.
    Uses LOG_LEVEL, LOG_FILE, and LOG_CONSOLE from environment.
    """
    global _CONFIGURED
    if _CONFIGURED:
        return
    _CONFIGURED = True

    from config import DEFAULT_LOG_FILE, DEFAULT_LOG_LEVEL, get_env, get_project_root

    level_name: str = get_env("LOG_LEVEL", DEFAULT_LOG_LEVEL, str)
    log_file: str = get_env("LOG_FILE", DEFAULT_LOG_FILE, str)
    console: bool = get_env("LOG_CONSOLE", False, bool)

    level = getattr(logging, level_name.upper(), logging.INFO)

    root = logging.getLogger(_LOGGER_NS)
    root.setLevel(level)

    fmt = logging.Formatter(
        "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    if log_file:
        project_root = get_project_root()
        fh = logging.FileHandler(project_root / log_file, encoding="utf-8")
        fh.setLevel(level)
        fh.setFormatter(fmt)
        root.addHandler(fh)

    if console:
        ch = logging.StreamHandler(sys.stderr)
        ch.setLevel(level)
        ch.setFormatter(fmt)
        root.addHandler(ch)


[docs] def get_logger(name: str) -> logging.Logger: """Return a child logger under the ``differential_lab`` namespace. Args: name: Module name (typically ``__name__``). Returns: A ``logging.Logger`` instance. """ _setup_root_logger() qualified = name if name.startswith(f"{_LOGGER_NS}.") else f"{_LOGGER_NS}.{name}" return logging.getLogger(qualified)