fix(backend): emit app.* INFO logs to stdout on the API process (#1926) #1941

Merged
bot-backend merged 1 commit from fix/1926-app-log-stdout into main 2026-06-27 02:59:09 +00:00
2 changed files with 48 additions and 0 deletions

View file

@ -48,6 +48,24 @@ from app.observability.sentry_scrub import scrub_sensitive_query
logger = logging.getLogger(__name__)
# App-level логи в stdout контейнера (#1926 observability). По умолчанию у app.*
# логгеров нет StreamHandler → их INFO теряется (в `docker logs` видны только
# uvicorn-логгеры), поэтому полезные строки (напр. "OSRM road-distance applied",
# RBAC, analyze) невидимы при отладке на VPS. Вешаем один StreamHandler на корневой
# "app" логгер на уровне APP_LOG_LEVEL (default INFO). propagate оставляем True →
# Sentry/GlitchTip LoggingIntegration (хендлер на root) продолжает получать
# breadcrumbs/events. Двойного stdout нет: у root своего stdout-хендлера нет.
# Идемпотентно (маркер _gd_app_stream — guard против дубля при reload/повторном
# импорте). Только API-процесс (main.py); worker (celery_app.py) НЕ трогаем —
# там loader'ы чатятся per-sync, INFO бы зашумил.
_app_logger = logging.getLogger("app")
_app_logger.setLevel(getattr(logging, os.getenv("APP_LOG_LEVEL", "INFO").upper(), logging.INFO))
if not any(getattr(_h, "_gd_app_stream", False) for _h in _app_logger.handlers):
_stream = logging.StreamHandler()
_stream.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
_stream._gd_app_stream = True # type: ignore[attr-defined] # idempotency marker
_app_logger.addHandler(_stream)
# Инициализируем SDK до создания FastAPI app, чтобы все инструменты
# (middleware, маршруты) видели активный client с самого старта процесса.
# GlitchTip не поддерживает profiling — profiles_sample_rate=0.0.

View file

@ -0,0 +1,30 @@
"""#1926 observability: app-logger StreamHandler в main.py (логи app.* в stdout).
Без него INFO от `logging.getLogger("app.*")` терялся (в `docker logs` видны были
только uvicorn-логгеры), и полезные строки (OSRM road-distance applied, RBAC,
analyze) были невидимы при отладке на VPS.
"""
from __future__ import annotations
import logging
def test_app_logger_has_stdout_handler() -> None:
import app.main # noqa: F401 — module-level logging setup выполняется на импорте
app_logger = logging.getLogger("app")
# Уровень INFO (default APP_LOG_LEVEL).
assert app_logger.level == logging.INFO
# Ровно один маркированный StreamHandler — идемпотентность (повторный импорт
# не плодит дубли).
marked = [h for h in app_logger.handlers if getattr(h, "_gd_app_stream", False)]
assert len(marked) == 1
assert isinstance(marked[0], logging.StreamHandler)
# propagate=True → Sentry/GlitchTip LoggingIntegration (хендлер на root)
# продолжает получать breadcrumbs/events; stdout-дубля нет (у root своего
# stdout-хендлера нет).
assert app_logger.propagate is True