DispelEvilBot/admin/routes/logs.py

119 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Раздел «Логи»: события из ClickHouse с фильтрами и пагинацией."""
from __future__ import annotations
import json
import logging
from datetime import UTC, datetime, timedelta
from typing import Any
from urllib.parse import urlencode
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from common import __version__
from common.analytics import ALL_EVENT_TYPES
from common.analytics import read as analytics_read
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/logs", tags=["logs"])
#: Сколько событий показывать на странице.
PAGE_SIZE = 50
def _pretty_payload(payload: str) -> str:
"""Отформатировать JSON-payload для просмотра; битый JSON — как есть."""
try:
return json.dumps(json.loads(payload), ensure_ascii=False, indent=2)
except (TypeError, ValueError):
return payload or ""
def _decorate(row: dict[str, Any], settings: Any) -> dict[str, Any]:
"""Добавить локальное время и читаемый payload."""
moment = row.get("event_time")
if isinstance(moment, datetime):
if moment.tzinfo is None:
moment = moment.replace(tzinfo=UTC)
time_local = moment.astimezone(settings.tzinfo).strftime("%d.%m.%Y %H:%M:%S")
else:
time_local = "—"
return {
**row,
"time_local": time_local,
"payload_pretty": _pretty_payload(str(row.get("payload") or "")),
}
@router.get("", response_class=HTMLResponse)
async def logs_page(
request: Request,
event_type: str = "",
user: str = "",
date_from: str = "",
date_to: str = "",
page: int = 1,
) -> HTMLResponse:
"""Показать события с фильтрами по типу, пользователю и датам."""
settings = request.app.state.settings
today = datetime.now(settings.tzinfo).date()
date_from = date_from or (today - timedelta(days=6)).isoformat()
date_to = date_to or today.isoformat()
page = max(page, 1)
events: list[dict[str, Any]] = []
total = 0
error: str | None = None
try:
events, total = await analytics_read.fetch_events(
settings,
event_type=event_type,
user=user,
date_from=date_from,
date_to=date_to,
limit=PAGE_SIZE,
offset=(page - 1) * PAGE_SIZE,
)
except ValueError:
error = "Даты должны быть в формате ГГГГ-ММ-ДД."
except Exception:
logger.exception("Не удалось прочитать события из ClickHouse")
error = "ClickHouse недоступен — логи временно не читаются."
pages = max((total + PAGE_SIZE - 1) // PAGE_SIZE, 1)
query_base = urlencode(
{
key: value
for key, value in {
"event_type": event_type,
"user": user,
"date_from": date_from,
"date_to": date_to,
}.items()
if value
}
)
return request.app.state.templates.TemplateResponse(
request=request,
name="logs/list.html",
context={
"version": __version__,
"events": [_decorate(row, settings) for row in events],
"event_types": sorted(ALL_EVENT_TYPES),
"event_type": event_type,
"user": user,
"date_from": date_from,
"date_to": date_to,
"page": page,
"pages": pages,
"total": total,
"error": error,
"query_base": query_base,
},
)