| """ |
| 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 |
|
|
| |
| _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] |
|
|
| |
| if level is None: |
| try: |
| from src.shared.config import get_settings |
|
|
| 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) |
|
|
| |
| 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) |
|
|
| |
| logger.propagate = False |
|
|
| _loggers[name] = logger |
| return logger |
|
|