"""Единая настройка логирования ВЕКТОРа (B22). Все модульные логгеры проекта — `logging.getLogger('vector.<модуль>')` — конфигурируются один раз при старте приложения (backend.main → setup_logging()). Формат: время, уровень, логгер, сообщение. Вывод — stdout (контейнер), дополнительно файл /tmp/vector/vector.log внутри контейнера (ротация 5×2 МБ). Наблюдаемые события (ключевые слова для grep по логам): vector.analyze — анализ: case_id, радиус, зоны, время расчёта vector.auth — login_ok / login_failed / lockout / logout vector.contour — отправка зон в КОНТУР (ok/failed) vector.admin — синхронизация закрытия кейса vector.audit — запись аудит-события (кто, что) vector.geo — геослой: PostGIS-режим, fallback на Overpass, ошибки """ from __future__ import annotations import logging import logging.handlers import os from pathlib import Path _LOG_FORMAT = '%(asctime)s %(levelname)s %(name)s: %(message)s' _LOG_DATEFMT = '%Y-%m-%d %H:%M:%S' # Порог вывода в stdout: INFO на сервере, DEBUG при VectorLOGLEVEL=debug _LEVEL = os.getenv('VectorLOGLEVEL', 'info').upper() # Файл-хэндлер: по умолчанию включён (внутри контейнера /tmp/vector). _FILE_ENABLED = os.getenv('VectorLOGFILE', '1').strip().lower() not in {'0', 'false', 'no'} _FILE_DIR = Path(os.getenv('VectorLOGDIR', '/tmp/vector')) def setup_logging() -> None: """Конфигурация корневого пакета 'vector'. Идемпотентна.""" root = logging.getLogger('vector') if getattr(root, '_vector_configured', False): return root.setLevel(logging.DEBUG) fmt = logging.Formatter(_LOG_FORMAT, datefmt=_LOG_DATEFMT) stream = logging.StreamHandler() stream.setLevel(getattr(logging, _LEVEL if _LEVEL in {'DEBUG', 'INFO', 'WARNING', 'ERROR'} else 'INFO')) stream.setFormatter(fmt) root.addHandler(stream) if _FILE_ENABLED: try: _FILE_DIR.mkdir(parents=True, exist_ok=True) file_handler = logging.handlers.RotatingFileHandler( _FILE_DIR / 'vector.log', maxBytes=2 * 1024 * 1024, backupCount=5, encoding='utf-8', ) file_handler.setLevel(logging.INFO) file_handler.setFormatter(fmt) root.addHandler(file_handler) except OSError: root.warning('Файл-лог недоступен (%s) — только stdout', _FILE_DIR) # uvicorn-логи ошибок приложения (traceback-и) идут тем же форматом uvicorn_error = logging.getLogger('uvicorn.error') uvicorn_error.handlers.clear() uvicorn_error.propagate = True root._vector_configured = True # type: ignore[attr-defined] def get_logger(name: str) -> logging.Logger: """Модульный логгер: get_logger('analyze') → 'vector.analyze'.""" short = name.removeprefix('vector.') return logging.getLogger(f'vector.{short}')