"""Logging unificado para Studio, Runtime y tools. Sprint 0: configuración mínima. Sprint 4+ agregará rotación, file handlers, journald, etc. """ from __future__ import annotations import logging import sys from typing import Final _DEFAULT_FORMAT: Final[str] = ( "%(asctime)s %(levelname)-7s %(name)-30s :: %(message)s" ) _DEFAULT_DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S" _configured: bool = False def setup_logging(level: int = logging.INFO, *, verbose: bool = False) -> None: """Configura el root logger una sola vez. Idempotente: llamadas posteriores son no-op (a menos que se cambie level). """ global _configured root = logging.getLogger() if _configured: root.setLevel(level) return handler = logging.StreamHandler(sys.stderr) handler.setFormatter(logging.Formatter(_DEFAULT_FORMAT, datefmt=_DEFAULT_DATEFMT)) root.addHandler(handler) root.setLevel(logging.DEBUG if verbose else level) _configured = True def get_logger(name: str) -> logging.Logger: """Logger de módulo. Asegura que setup_logging() se haya llamado.""" if not _configured: setup_logging() return logging.getLogger(name)