This commit is contained in:
Mikan
2026-06-19 16:48:07 +03:00
parent 5a78def096
commit a69189c85b
4 changed files with 63 additions and 8 deletions

View File

@@ -1,4 +1,18 @@
"""Structured logging setup."""
"""Structured logging setup.
`LOG_LEVEL` (env) controls the verbosity of:
- The root Python logger
- structlog-bound loggers (app.*)
- Uvicorn's own loggers (`uvicorn`, `uvicorn.access`, `uvicorn.error`,
`uvicorn.asgi`) — these otherwise stay at INFO regardless of LOG_LEVEL
because uvicorn configures them itself at startup, before our lifespan
calls setup_logging(). We forcibly re-level them here.
- The `sqlalchemy.engine` logger (kept at WARNING unless LOG_LEVEL=DEBUG).
Note: `logging.basicConfig()` is a no-op once the root logger has been
configured (which uvicorn does at import time), so it alone is NOT enough
to honor LOG_LEVEL — we must also call `setLevel()` on each named logger.
"""
from __future__ import annotations
import logging
@@ -9,15 +23,46 @@ import structlog
from app.config import settings
# Loggers whose level must be forced to LOG_LEVEL (uvicorn pre-configures them
# at INFO before our lifespan runs, so basicConfig cannot change them).
_FORCED_LOGGERS = (
"uvicorn",
"uvicorn.access",
"uvicorn.error",
"uvicorn.asgi",
"fastapi",
)
def setup_logging() -> None:
level = getattr(logging, settings.log_level.upper(), logging.INFO)
# Force the root logger level (affects any logger that doesn't override).
logging.getLogger().setLevel(level)
# Also call basicConfig for the formatter (idempotent if already set up).
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=level,
force=True, # python 3.8+: re-init even if already configured
)
# Force level on loggers that uvicorn pre-configured.
for name in _FORCED_LOGGERS:
lg = logging.getLogger(name)
lg.setLevel(level)
# Ensure uvicorn access logs propagate to the root handler.
lg.propagate = True
for h in lg.handlers:
h.setLevel(level)
# SQLAlchemy is chatty at INFO; keep it at WARNING unless explicitly DEBUG.
sa_level = logging.DEBUG if level <= logging.DEBUG else logging.WARNING
logging.getLogger("sqlalchemy.engine").setLevel(sa_level)
logging.getLogger("sqlalchemy.pool").setLevel(sa_level)
logging.getLogger("asyncpg").setLevel(sa_level)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,