"""Structured logging configuration using structlog."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
import structlog
from litestar.logging.config import LoggingConfig, StructlogEventFilter, StructLoggingConfig
from litestar.middleware.logging import LoggingMiddlewareConfig
from litestar.plugins.structlog import StructlogConfig, StructlogPlugin
from structlog.dev import RichTracebackFormatter
if TYPE_CHECKING:
from structlog.typing import Processor
def _get_console_processors() -> list[Processor]:
"""Get processors for pretty console output with Rich."""
return [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.dev.ConsoleRenderer(
colors=True,
exception_formatter=RichTracebackFormatter(
width=120,
show_locals=True,
max_frames=20,
),
),
]
def _get_json_processors() -> list[Processor]:
"""Get processors for JSON output in production."""
return [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.format_exc_info,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
]
def _get_stdlib_processors(*, use_json: bool) -> list[Processor]:
"""Get processors for stdlib logging integration."""
if use_json:
return [
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.stdlib.ExtraAdder(),
StructlogEventFilter(["color_message"]),
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.JSONRenderer(),
]
return [
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.stdlib.ExtraAdder(),
StructlogEventFilter(["color_message"]),
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.dev.ConsoleRenderer(
colors=True,
exception_formatter=RichTracebackFormatter(
width=120,
show_locals=True,
max_frames=20,
),
),
]
[docs]
def get_logger(name: str | None = None) -> Any:
"""Get a structlog logger instance.
Args:
name: Logger name. If None, uses the root logger.
Returns:
A structlog logger instance
"""
return structlog.get_logger(name)
[docs]
def bind_correlation_id(correlation_id: str) -> None:
"""Bind a correlation ID to the current context.
Args:
correlation_id: Unique identifier to correlate related log entries
"""
structlog.contextvars.bind_contextvars(correlation_id=correlation_id)
[docs]
def unbind_correlation_id() -> None:
"""Remove the correlation ID from the current context."""
structlog.contextvars.unbind_contextvars("correlation_id")
[docs]
def bind_user_context(user_id: int | str, username: str | None = None) -> None:
"""Bind user context to the current logging context.
Args:
user_id: User identifier
username: Username (optional)
"""
context = {"user_id": user_id}
if username:
context["username"] = username
structlog.contextvars.bind_contextvars(**context)
[docs]
def unbind_user_context() -> None:
"""Remove user context from the current logging context."""
structlog.contextvars.unbind_contextvars("user_id", "username")