"""Structured logging setup using structlog.""" import logging import sys from functools import lru_cache import structlog def configure_logging(log_level: str = "INFO", log_json: bool = True) -> None: """Configure structlog with JSON or console output. Args: log_level: Logging level (DEBUG, INFO, WARNING, ERROR) log_json: If True, output JSON logs; otherwise, use colored console output """ # Set up standard library logging logging.basicConfig( format="%(message)s", stream=sys.stdout, level=getattr(logging, log_level.upper(), logging.INFO), ) # Common processors shared_processors: list[structlog.typing.Processor] = [ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.StackInfoRenderer(), structlog.dev.set_exc_info, structlog.processors.TimeStamper(fmt="iso"), ] if log_json: # JSON output for production structlog.configure( processors=[ *shared_processors, structlog.processors.dict_tracebacks, structlog.processors.JSONRenderer(), ], wrapper_class=structlog.make_filtering_bound_logger( getattr(logging, log_level.upper(), logging.INFO) ), context_class=dict, logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=True, ) else: # Colored console output for development structlog.configure( processors=[ *shared_processors, structlog.dev.ConsoleRenderer(colors=True), ], wrapper_class=structlog.make_filtering_bound_logger( getattr(logging, log_level.upper(), logging.INFO) ), context_class=dict, logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=True, ) @lru_cache def get_logger(name: str = "agent") -> structlog.stdlib.BoundLogger: """Get a bound logger instance. Args: name: Logger name for context Returns: Configured structlog bound logger """ return structlog.get_logger(name)