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>
This commit is contained in:
Jason Staack
2026-03-08 17:46:37 -05:00
commit b840047e19
511 changed files with 106948 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
"""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)