LIBRE / src /shared /logger.py
RyZ
feat: adding full working local ETL Pipeline
e391a84
Raw
History Blame Contribute Delete
2.14 kB
"""
shared/logger.py
────────────────
Logging factory with a consistent format for all modules.
Usage:
from src.shared.logger import get_logger
logger = get_logger(__name__)
logger.info("Signal ingested", extra={"device_id": "sensor-001"})
"""
from __future__ import annotations
import logging
import sys
from typing import Optional
# Module-level cache so duplicate calls reuse the same logger object
_loggers: dict[str, logging.Logger] = {}
def get_logger(
name: str,
level: Optional[str] = None,
) -> logging.Logger:
"""
Return a configured logger for *name*.
The first call for a given name creates and caches the logger;
subsequent calls return the cached instance.
Args:
name: Logger name, typically ``__name__`` of the calling module.
level: Override log level (e.g. ``"DEBUG"``). If *None*, the level
is read from the ``LOG_LEVEL`` environment variable via
:func:`~src.shared.config.get_settings`, defaulting to INFO.
Returns:
Configured :class:`logging.Logger` instance.
"""
if name in _loggers:
return _loggers[name]
# Resolve level
if level is None:
try:
from src.shared.config import get_settings # lazy import to avoid cycles
level = get_settings().log_level
except Exception:
level = "INFO"
numeric_level = getattr(logging, level.upper(), logging.INFO)
logger = logging.getLogger(name)
logger.setLevel(numeric_level)
# Avoid adding duplicate handlers if the logger already has them
if not logger.handlers:
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(numeric_level)
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# Don't propagate to the root logger to avoid duplicate output
logger.propagate = False
_loggers[name] = logger
return logger