Files
Solo_bot/middlewares/loggings.py
T
2026-03-18 04:13:57 +03:00

164 lines
6.2 KiB
Python

import asyncio
from collections.abc import Awaitable, Callable
from typing import Any, TypedDict
from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, InlineQuery, Message, TelegramObject, User
from audit import (
ensure_telegram_context,
log_telegram_access,
record_audit_event_to_redis,
record_telegram_access_event,
record_telegram_access_event_background,
set_telegram_actor,
_telegram_access_payload,
)
try:
from core.cache_config import AUDIT_REDIS_BUFFER_ENABLED
except ImportError:
AUDIT_REDIS_BUFFER_ENABLED = False
from logger import logger
class UserInfo(TypedDict):
user_id: int | None
username: str | None
action: str | None
def _log_activity_sync(user_info: UserInfo) -> None:
"""Синхронный вывод в лог, чтобы не блокировать event loop в create_task."""
logger.info(
f"Активность пользователя │ "
f"ID: {str(user_info['user_id']).ljust(10)}"
f"Имя: {user_info['username'] or '':<15}"
f"Действие: {user_info['action'] or ''}"
)
class LoggingMiddleware(BaseMiddleware):
"""Middleware для логирования действий пользователя. Лог пишется в фоне.
Аудит: при включённом Redis-буфере пишем только в Redis (в БД — раз в сутки через drain);
при выключенном буфере или сбое Redis — пишем в БД."""
def __init__(self, sessionmaker=None):
super().__init__()
self._sessionmaker = sessionmaker
async def __call__(
self,
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: dict[str, Any],
) -> Any:
audit_context = ensure_telegram_context(data, event)
user_info = self._extract_user_info(event)
if user_info["user_id"]:
asyncio.create_task(asyncio.to_thread(_log_activity_sync, user_info))
try:
result = await handler(event, data)
db_user = data.get("user")
if isinstance(db_user, dict):
set_telegram_actor(
audit_context,
identity_id=db_user.get("identity_id"),
tg_id=db_user.get("tg_id"),
)
payload = _telegram_access_payload(audit_context, event, result="success")
if AUDIT_REDIS_BUFFER_ENABLED:
if self._sessionmaker is not None:
asyncio.create_task(
record_telegram_access_event_background(
self._sessionmaker, **payload
)
)
else:
asyncio.create_task(record_audit_event_to_redis(**payload))
else:
if self._sessionmaker is not None:
asyncio.create_task(
record_telegram_access_event_background(
self._sessionmaker, **payload
)
)
else:
session = data.get("session")
if session is not None:
await record_telegram_access_event(
session,
audit_context,
event,
result="success",
)
asyncio.create_task(
asyncio.to_thread(
log_telegram_access,
event,
audit_context=audit_context,
result="success",
)
)
return result
except Exception as exc:
reason = type(exc).__name__
payload = _telegram_access_payload(
audit_context, event, result="fail", reason=reason
)
if AUDIT_REDIS_BUFFER_ENABLED:
if self._sessionmaker is not None:
asyncio.create_task(
record_telegram_access_event_background(
self._sessionmaker, **payload
)
)
else:
asyncio.create_task(record_audit_event_to_redis(**payload))
else:
if self._sessionmaker is not None:
asyncio.create_task(
record_telegram_access_event_background(
self._sessionmaker, **payload
)
)
else:
session = data.get("session")
if session is not None:
await record_telegram_access_event(
session,
audit_context,
event,
result="fail",
reason=reason,
)
asyncio.create_task(
asyncio.to_thread(
log_telegram_access,
event,
audit_context=audit_context,
result="fail",
reason=reason,
)
)
raise
def _extract_user_info(self, event: TelegramObject) -> UserInfo:
"""Извлекает информацию о пользователе из различных типов событий."""
result: UserInfo = {"user_id": None, "username": None, "action": None}
if hasattr(event, "from_user") and isinstance(event.from_user, User):
result["user_id"] = event.from_user.id
result["username"] = event.from_user.username
if isinstance(event, Message):
result["action"] = f"Сообщение: {event.text}"
elif isinstance(event, CallbackQuery):
result["action"] = f"Обратный вызов: {event.data}"
elif isinstance(event, InlineQuery):
result["action"] = f"Inline запрос: {event.query}"
return result