Source code for veupath_chatbot.platform.logging
"""Structured logging configuration."""
import logging
import sys
import structlog
from structlog.types import EventDict, Processor
from veupath_chatbot.platform.config import get_settings
[docs]
def add_request_id(
logger: logging.Logger, _method_name: str, event_dict: EventDict
) -> EventDict:
"""Add request ID to log entries if available.
:param logger: Logger instance.
:param _method_name: Method name (structlog processor convention).
:param event_dict: Log event dictionary to modify.
:returns: Updated event dict.
"""
from veupath_chatbot.platform.context import request_id_ctx
request_id = request_id_ctx.get()
if request_id:
event_dict["request_id"] = request_id
return event_dict
[docs]
def setup_logging() -> None:
"""Configure structured logging."""
settings = get_settings()
# Common processors
shared_processors: list[Processor] = [
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.UnicodeDecoder(),
add_request_id,
]
if settings.log_format == "json":
# JSON formatting for production
renderer: Processor = structlog.processors.JSONRenderer()
else:
# Console formatting for development
renderer = structlog.dev.ConsoleRenderer(colors=True)
structlog.configure(
processors=[
*shared_processors,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
# Configure standard library logging
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(
structlog.stdlib.ProcessorFormatter(
foreign_pre_chain=shared_processors,
processors=[
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
renderer,
],
)
)
root_logger = logging.getLogger()
root_logger.addHandler(handler)
root_logger.setLevel(settings.log_level)
# Quiet noisy loggers
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
[docs]
def get_logger(name: str) -> structlog.BoundLogger:
"""Get a structlog logger.
:param name: Logger name (typically __name__).
:returns: Configured bound logger.
"""
from typing import cast
logger = structlog.get_logger(name)
# structlog.get_logger returns a BoundLogger after configuration
return cast(structlog.BoundLogger, logger)