DispelEvilBot/bot/services/heartbeat.py

73 lines
2.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.

"""Heartbeat для Docker healthcheck и ежечасный пинг «бот живой».
Бот периодически обновляет mtime файла; healthcheck считает контейнер живым,
если файл свежий. Если event loop завис — heartbeat перестаёт обновляться.
Раз в час в чат логов (``ALERT_CHAT_ID``) уходит короткое сообщение о работе бота,
и в аналитику пишется событие ``bot_alive``.
"""
from __future__ import annotations
import asyncio
import logging
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING
from bot.services.alerts import notify_alert
from bot.services.runtime import uptime_human, uptime_seconds
from common import __version__
from common.analytics import EventType, record
from common.config import get_settings
from common.texts import alerts as alerts_text
from common.texts.render import render
if TYPE_CHECKING:
from aiogram import Bot
logger = logging.getLogger(__name__)
#: Как часто обновлять heartbeat-файл.
HEARTBEAT_INTERVAL = 30
#: Час (в таймзоне бота), за который пинг уже отправлен.
_last_ping_hour: datetime | None = None
async def ping_hourly(bot: Bot, now: datetime | None = None) -> bool:
"""Записать событие ``bot_alive`` и отправить пинг в чат логов раз в час.
Возвращает ``True``, если в этом часу пинг отправлен (или предпринята попытка).
Повторные вызовы в течение того же часа ничего не делают.
"""
global _last_ping_hour
settings = get_settings()
current_hour = (now or datetime.now(settings.tzinfo)).replace(minute=0, second=0, microsecond=0)
if current_hour == _last_ping_hour:
return False
_last_ping_hour = current_hour
record(
EventType.BOT_ALIVE,
payload={"app_env": settings.app_env, "uptime_seconds": uptime_seconds()},
)
text = render(
alerts_text.MSG_ALIVE,
{"version": __version__, "uptime": uptime_human()},
)
return await notify_alert(bot, text)
async def run_heartbeat(bot: Bot) -> None:
"""Обновлять heartbeat-файл каждые ``HEARTBEAT_INTERVAL`` секунд и пинговать раз в час."""
path = Path(get_settings().heartbeat_path)
while True:
try:
path.touch()
except OSError:
logger.exception("Не удалось обновить heartbeat-файл %s", path)
await ping_hourly(bot)
await asyncio.sleep(HEARTBEAT_INTERVAL)