Files
the-other-dude/backend/app/logging_config.py
Jason Staack b840047e19 feat: The Other Dude v9.0.1 — full-featured email system
ci: add GitHub Pages deployment workflow for docs site

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:30:44 -05:00

82 lines
2.6 KiB
Python

"""Structured logging configuration for the FastAPI backend.
Uses structlog with two rendering modes:
- Dev mode (ENVIRONMENT=dev or DEBUG=true): colored console output
- Prod mode: machine-parseable JSON output
Must be called once during app startup (in lifespan), NOT at module import time,
so tests can override the configuration.
"""
import logging
import os
import structlog
def configure_logging() -> None:
"""Configure structlog for the FastAPI application.
Dev mode: colored console output with human-readable formatting.
Prod mode: JSON output with machine-parseable fields.
Must be called once during app startup (in lifespan), NOT at module import time,
so tests can override the configuration.
"""
is_dev = os.getenv("ENVIRONMENT", "dev") == "dev"
log_level_name = os.getenv("LOG_LEVEL", "debug" if is_dev else "info").upper()
log_level = getattr(logging, log_level_name, logging.INFO)
shared_processors: list[structlog.types.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(),
]
if is_dev:
renderer = structlog.dev.ConsoleRenderer()
else:
renderer = structlog.processors.JSONRenderer()
structlog.configure(
processors=[
*shared_processors,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
# Capture stdlib loggers (uvicorn, SQLAlchemy, alembic) into structlog pipeline
formatter = structlog.stdlib.ProcessorFormatter(
processors=[
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
renderer,
],
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.handlers.clear()
root_logger.addHandler(handler)
root_logger.setLevel(log_level)
# Quiet down noisy libraries in dev
if is_dev:
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger:
"""Get a structlog bound logger.
Use this instead of logging.getLogger() throughout the application.
"""
return structlog.get_logger(name)