# 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