116 lines
5.7 KiB
Python
116 lines
5.7 KiB
Python
# 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
|