"""Structured logging via structlog. Single setup function called from main scripts.""" from __future__ import annotations import logging import sys import structlog from shared.config import settings def setup_logging(level: str | None = None) -> structlog.stdlib.BoundLogger: """Configure structlog + stdlib logging once. Returns a base logger.""" log_level = (level or settings.log_level).upper() # Force UTF-8 stdout on Windows so Romanian/Cyrillic/etc. don't crash rich. try: sys.stdout.reconfigure(encoding="utf-8") # type: ignore[union-attr] sys.stderr.reconfigure(encoding="utf-8") # type: ignore[union-attr] except (AttributeError, OSError): pass logging.basicConfig( format="%(message)s", stream=sys.stdout, level=getattr(logging, log_level), ) # Silence overly chatty third-party loggers (httpx prints every request). for noisy in ("httpx", "httpcore", "urllib3"): logging.getLogger(noisy).setLevel(logging.WARNING) structlog.configure( processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.TimeStamper(fmt="iso", utc=True), structlog.dev.ConsoleRenderer(colors=False), ], wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, log_level)), cache_logger_on_first_use=True, ) return structlog.get_logger() def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger: """Get a logger; setup_logging() must have been called once first.""" return structlog.get_logger(name) if name else structlog.get_logger()