|
|
""" |
|
|
Structured JSON Logging with structlog |
|
|
""" |
|
|
import logging |
|
|
import sys |
|
|
from typing import Any |
|
|
|
|
|
import structlog |
|
|
from structlog.types import EventDict, Processor |
|
|
|
|
|
from .config import settings |
|
|
|
|
|
|
|
|
def add_correlation_id(logger: Any, method_name: str, event_dict: EventDict) -> EventDict: |
|
|
"""Add correlation ID to log entry if available""" |
|
|
|
|
|
import contextvars |
|
|
correlation_id = contextvars.ContextVar("correlation_id", default=None) |
|
|
if correlation_id: |
|
|
event_dict["correlation_id"] = correlation_id.get() |
|
|
return event_dict |
|
|
|
|
|
|
|
|
def drop_color_message_key(logger: Any, method_name: str, event_dict: EventDict) -> EventDict: |
|
|
"""Drop color key for non-dev environments""" |
|
|
event_dict.pop("color_message", None) |
|
|
return event_dict |
|
|
|
|
|
|
|
|
def configure_logging() -> None: |
|
|
"""Configure structured JSON logging""" |
|
|
shared_processors: list[Processor] = [ |
|
|
structlog.contextvars.merge_contextvars, |
|
|
structlog.stdlib.add_logger_name, |
|
|
structlog.stdlib.add_log_level, |
|
|
structlog.processors.TimeStamper(fmt="iso"), |
|
|
structlog.processors.StackInfoRenderer(), |
|
|
structlog.processors.format_exc_info, |
|
|
add_correlation_id, |
|
|
] |
|
|
|
|
|
if settings.app_env == "local": |
|
|
|
|
|
renderer = structlog.dev.ConsoleRenderer( |
|
|
colors=settings.debug, |
|
|
exception_formatter=structlog.dev.plain_traceback |
|
|
) |
|
|
else: |
|
|
|
|
|
renderer = structlog.processors.JSONRenderer() |
|
|
shared_processors.append(drop_color_message_key) |
|
|
|
|
|
structlog.configure( |
|
|
processors=shared_processors + [structlog.stdlib.ProcessorFormatter.wrap_renderer(renderer)], |
|
|
wrapper_class=structlog.stdlib.BoundLogger, |
|
|
context_class=dict, |
|
|
logger_factory=structlog.stdlib.LoggerFactory(), |
|
|
cache_logger_on_first_use=True, |
|
|
) |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
format="%(message)s", |
|
|
stream=sys.stdout, |
|
|
level=getattr(logging, settings.log_level.upper()), |
|
|
) |
|
|
|
|
|
|
|
|
for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "fastapi"]: |
|
|
logging.getLogger(logger_name).setLevel(logging.WARNING) |
|
|
|
|
|
|
|
|
def get_logger(name: str) -> structlog.stdlib.BoundLogger: |
|
|
"""Get a structured logger""" |
|
|
return structlog.get_logger(name) |
|
|
|