B22: прикладное логирование vector.* + администрирование в доках (по образцу РВС)

- backend/logging_setup.py: setup_logging() (stdout INFO + файл /tmp/vector/vector.log,
  ротация 5×2 МБ, VectorLOGLEVEL/VectorLOGFILE/VectorLOGDIR), get_logger('vector.*')
- события: vector.analyze (матрасчёт с длительностью), vector.auth (login_ok/failed/
  lockout), vector.audit (каждая запись), vector.contour (ok/failed/rejected),
  vector.operations (operation_complete), vector.geocode (успех/timeout/ошибка)
- существующие логгеры переведены в namespace vector.* (geo, geo.osm, rules, water, admin)
- README §13а: таблица наблюдаемых событий + правила (без ПДн/секретов)
- SETUP_GUIDE: §15 RPO/RTO + backup≠restore, §16 grep по vector.*, §17 диагностика
  по цепочкам (БД, 500, fallback-зоны, вход), §17а чек-лист передачи проекта
- DEPLOY.md: вместо устаревшего дубля — ссылка на актуальные доки + шпаргалка стенда
- тесты: 239 passed
This commit is contained in:
2026-09-25 09:38:39 +03:00
parent 1513bad538
commit d5a270f3af
16 changed files with 345 additions and 117 deletions
+14
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any
from uuid import UUID
@@ -8,6 +10,7 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from backend.database import db
from backend.logging_setup import get_logger
from backend.routers.auth import require_roles
from services.rules_analysis import rules_analyze
from services.search_engine import (
@@ -15,6 +18,8 @@ from services.search_engine import (
build_search_model,
)
log = get_logger('analyze')
router = APIRouter(prefix='/api/v1/analyze', tags=['analyze'])
@@ -44,6 +49,8 @@ class AnalysisRequest(BaseModel):
async def analyze_case(payload: AnalysisRequest) -> dict[str, Any]:
# B14: роутер — тонкая обёртка. Вся расчётная логика — в
# services/search_engine.py::build_search_model (без DB/auth/HTTP).
import time as _time
_t0 = _time.monotonic()
case_data: dict[str, Any] = {
'age': payload.age,
'gender': payload.gender,
@@ -109,6 +116,13 @@ async def analyze_case(payload: AnalysisRequest) -> dict[str, Any]:
result['case_id'] = str(payload.case_id) if payload.case_id else None
result['analyzed_at'] = datetime.now(timezone.utc).isoformat()
log.info(
'анализ case=%s радиус=%.1fкм срочность=%s зон=%d время=%.0fмс',
payload.case_id or '—', result.get('search_radius_km') or 0,
result.get('urgency'), len(result.get('primary_zones') or []),
(_time.monotonic() - _t0) * 1000,
)
if payload.case_id is not None:
db.update_case(str(payload.case_id), analysis_log=result, status='analyzed')