fix(logger): вызывать setup_logger при импорте приложения (#63)

setup_logger() с diagnose=False существовал, но не вызывался: работал
обработчик loguru по умолчанию (diagnose=True), и трейсбек любого
залогированного исключения содержал значения локальных переменных.
Теперь src/app.py вызывает setup_logger при импорте, как tabular-parser.

Регрессионный тест в отдельном процессе: исключение с локальной
переменной логируется с трейсбеком, но без её значения.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-08 16:41:40 +04:00
parent d8bf5b22dc
commit c937c8173f
2 changed files with 55 additions and 1 deletions

View File

@ -13,7 +13,8 @@ middleware не подключается), healthcheck (uptime, version, nats: o
`recognition.result` (ack и результаты сервисов) обрабатывает inbox `handle_message` того же модуля с фабрикой сессий `recognition.result` (ack и результаты сервисов) обрабатывает inbox `handle_message` того же модуля с фабрикой сессий
приложения (inbox меняет задание и статус файла; маппинг результата в доменные таблицы — отдельный цикл, Срез 2c D-1). приложения (inbox меняет задание и статус файла; маппинг результата в доменные таблицы — отдельный цикл, Срез 2c D-1).
Остановка: фоновые задачи обмена → NATS → S3 → SMB → пул соединений БД; каждый шаг независим — ошибка одного Остановка: фоновые задачи обмена → NATS → S3 → SMB → пул соединений БД; каждый шаг независим — ошибка одного
логируется и не отменяет остальные. логируется и не отменяет остальные. Логгер настраивает `setup_logger` при импорте модуля: без значений локальных
переменных в трейсбеках (#63).
""" """
import asyncio import asyncio
@ -41,6 +42,7 @@ from src.api.router import API_PREFIX, build_api_router
from src.core.clients import S3Storage from src.core.clients import S3Storage
from src.core.config import Settings, get_settings from src.core.config import Settings, get_settings
from src.core.exception_handlers import register_exception_handlers from src.core.exception_handlers import register_exception_handlers
from src.core.logger import setup_logger
from src.core.nats import MessageHandler, NatsConnection from src.core.nats import MessageHandler, NatsConnection
from src.db.db import async_session_maker, engine, session_transaction from src.db.db import async_session_maker, engine, session_transaction
from src.services.recognition_exchange import ExchangeTasks, handle_message from src.services.recognition_exchange import ExchangeTasks, handle_message
@ -51,6 +53,10 @@ settings = get_settings()
VERSION = settings.VERSION VERSION = settings.VERSION
UP_TIME = datetime.now(UTC) UP_TIME = datetime.now(UTC)
# Свой обработчик loguru вместо стандартного: diagnose=False — значения локальных переменных не попадают в
# трейсбеки (#63)
setup_logger(level="DEBUG" if settings.DEBUG else "INFO")
# --- РАИСА: защищённый API (только вне DEBUG) --------------------------------------------------- # --- РАИСА: защищённый API (только вне DEBUG) ---------------------------------------------------
protected_middleware_available = False protected_middleware_available = False

View File

@ -5,8 +5,11 @@
import asyncio import asyncio
import importlib import importlib
import subprocess
import sys
import threading import threading
from http import HTTPStatus from http import HTTPStatus
from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any from typing import Any
@ -701,3 +704,48 @@ async def test_forwarded_for_only_from_trusted_proxy(tmp_path: Any, trusted: str
def test_forwarded_allow_ips_default_is_loopback() -> None: def test_forwarded_allow_ips_default_is_loopback() -> None:
assert Settings.model_fields["FORWARDED_ALLOW_IPS"].default == "127.0.0.1" assert Settings.model_fields["FORWARDED_ALLOW_IPS"].default == "127.0.0.1"
# --- Логгер: значения локальных переменных не попадают в лог (#63) ----------------------------------------------------
_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[1],
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