""" 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