diff --git a/api/src/app.py b/api/src/app.py index 23a872d..808738c 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -10,6 +10,7 @@ HTTP-сервер жив. Остановка: отмена подключения, остановка воркера (задания в работе отменяются), закрытие NATS и S3. - Объекты lifespan доступны в `app.state`: `worker`, `nats`, `storage`, `normalizer`, `health`. +- При импорте модуля — `setup_logger` (без значений локальных переменных в трейсбеках), затем `app = create_app()`. - `NATS_ENABLED=false` — подключения нет (`nats = disabled`), воркер запросов не получает (dev без NATS). """ @@ -28,6 +29,7 @@ from src.core.clients import S3Storage from src.core.config import Settings, get_settings from src.core.exception_handlers import register_exception_handlers from src.core.health import HealthProvider +from src.core.logger import setup_logger from src.core.nats import NatsGateway from src.core.worker import Worker from src.gateway import create_hotfolder @@ -116,4 +118,7 @@ def create_app(settings: Settings | None = None) -> FastAPI: return app +# Свой обработчик loguru вместо стандартного: diagnose=False — значения локальных переменных не попадают в +# трейсбеки (#18) +setup_logger(level="DEBUG" if get_settings().DEBUG else "INFO") app = create_app() diff --git a/api/tests/unit/test_app.py b/api/tests/unit/test_app.py index c9ad7da..9cbfc1f 100644 --- a/api/tests/unit/test_app.py +++ b/api/tests/unit/test_app.py @@ -2,6 +2,8 @@ воркер). NATS и S3 не нужны: в тестах lifespan `NATS_ENABLED=false` либо методы `NatsGateway` подменены.""" import asyncio +import subprocess +import sys from datetime import datetime from pathlib import Path from typing import Any @@ -228,3 +230,48 @@ def test_create_app_fails_on_broken_rules(tmp_path: Path) -> None: def test_create_app_uses_ocr_normalizer() -> None: app = create_app(get_settings().model_copy(update={"SCAN_HOTFOLDER_PATH": "."})) assert isinstance(app.state.normalizer, OcrNormalizer) + + +# --- Логгер: значения локальных переменных не попадают в лог (#18) ---------------------------------------------------- + +_LOG_EXCEPTION_SCRIPT = """ +import importlib +import os +import sys + +from loguru import logger + +sys.path.insert(0, os.getcwd()) +importlib.import_module("src.app") + + +def fail(): + secret = "local-secret-value" + raise ValueError("boom" if secret else "") + + +try: + fail() +except ValueError: + logger.exception("ошибка") +logger.complete() +""" + + +def test_app_import_logs_exceptions_without_local_values(tmp_path: Path) -> None: + """Импорт приложения настраивает loguru через setup_logger (diagnose=False): трейсбек есть, значений локальных + переменных в нём нет. Отдельный процесс — тесты перенастраивают обработчики loguru, здесь нужен чистый импорт. + Скрипт — файлом: без исходника (`python -c`) loguru не аннотирует строки и тест прошёл бы и с diagnose=True.""" + script = tmp_path / "log_exception.py" + script.write_text(_LOG_EXCEPTION_SCRIPT) + result = subprocess.run( + [sys.executable, str(script)], + cwd=Path(__file__).resolve().parents[2], + capture_output=True, + text=True, + timeout=60, + check=True, + ) + output = result.stdout + result.stderr + assert "ValueError: boom" in output + assert "local-secret-value" not in output