llm-agent-backend/app/heartbeat_monitor.py
2025-11-16 23:49:12 +03:00

116 lines
5.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.

# app/heartbeat_monitor.py
import threading
import time
import requests
import datetime
import logging
# import traceback
# Настройка логирования для HeartbeatMonitor
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Добавляем обработчик, если его еще нет (чтобы логи выводились в консоль)
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
class HeartbeatMonitor:
"""
Мониторит доступность заданных внешних эндпоинтов путем периодических HTTP-запросов.
Работает в отдельном фоновом потоке, логируя результаты.
"""
def __init__(self, interval_minutes: int = 5):
self._interval_seconds = interval_minutes * 60
self._target_urls = [
"https://render-service-gsu7.onrender.com/heartbeat",
# Добавьте сюда другие URL для мониторинга, если они появятся
]
self._running = False
self._thread = None
self._last_check_results = {}
logger.info(f"HeartbeatMonitor инициализирован с интервалом {interval_minutes} минут.")
logger.info(f"Мониторинг URL: {', '.join(self._target_urls)}")
# Вывод call stack
# stack_trace = "".join(traceback.format_stack())
# logger.info(f"Call stack при инициализации HeartbeatMonitor:\n{stack_trace}")
def _run_heartbeat_loop(self):
"""
Основной цикл мониторинга, выполняющийся в отдельном потоке.
"""
while self._running:
logger.info("Начинается выполнение Heartbeat-проверок...")
current_results = {}
overall_healthy = True
for url in self._target_urls:
start_time = time.time()
try:
response = requests.get(url, timeout=10) # Таймаут 10 секунд
if 200 <= response.status_code < 300:
status = {"healthy": True, "status_code": response.status_code, "message": "OK"}
logger.info(f"✅ Heartbeat {url}: OK (HTTP {response.status_code})")
else:
status = {"healthy": False, "status_code": response.status_code, "message": f"Non-2xx status code"}
logger.warning(f"⚠️ Heartbeat {url}: Ошибка (HTTP {response.status_code})")
overall_healthy = False
except requests.exceptions.RequestException as e:
status = {"healthy": False, "status_code": None, "message": f"Request failed: {e}"}
logger.error(f"❌ Heartbeat {url}: Ошибка запроса: {e}")
overall_healthy = False
except Exception as e:
status = {"healthy": False, "status_code": None, "message": f"Unexpected error: {e}"}
logger.critical(f"🔥 Heartbeat {url}: Неожиданная ошибка: {e}")
overall_healthy = False
finally:
status["timestamp"] = datetime.datetime.now().isoformat()
status["response_time_ms"] = int((time.time() - start_time) * 1000)
current_results[url] = status
self._last_check_results = current_results
if overall_healthy:
logger.info("Все Heartbeat-проверки успешно пройдены.")
else:
logger.warning("✖ Обнаружены проблемы в Heartbeat-проверках.")
time.sleep(self._interval_seconds)
def start(self):
"""
Запускает мониторинг в отдельном потоке.
"""
if not self._running:
self._running = True
# daemon=True позволяет приложению завершиться, даже если этот поток еще работает.
# Python автоматически завершит daemon-потоки при выходе из основной программы.
self._thread = threading.Thread(target=self._run_heartbeat_loop, daemon=True)
self._thread.start()
logger.info("HeartbeatMonitor запущен.")
else:
logger.warning("HeartbeatMonitor уже запущен.")
def stop(self):
"""
Останавливает поток мониторинга.
"""
if self._running:
logger.info("Остановка HeartbeatMonitor...")
self._running = False
if self._thread and self._thread.is_alive():
self._thread.join(timeout=5) # Даем потоку 5 секунд на завершение
if self._thread.is_alive():
logger.warning("HeartbeatMonitor поток не завершился в течение таймаута.")
logger.info("HeartbeatMonitor остановлен.")
else:
logger.warning("HeartbeatMonitor не был запущен.")
def get_last_check_results(self):
"""
Возвращает результаты последней проверки (для возможного использования внутри приложения).
"""
return self._last_check_results