Nanny7's picture
feat: Phase 5 complete - Full stack AI Todo application with Dapr and Kafka
5c244a3
"""
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"""
# This will be populated by middleware
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":
# Development: Pretty console output
renderer = structlog.dev.ConsoleRenderer(
colors=settings.debug,
exception_formatter=structlog.dev.plain_traceback
)
else:
# Production: JSON output
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,
)
# Configure standard logging
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=getattr(logging, settings.log_level.upper()),
)
# Silence noisy loggers
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)