Add heartbeat monitor
This commit is contained in:
parent
064ae9f6bf
commit
7f4c004032
115
app/heartbeat_monitor.py
Normal file
115
app/heartbeat_monitor.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# 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
|
||||
34
run.py
34
run.py
|
|
@ -1,22 +1,38 @@
|
|||
# ----------------------------------------------- Imports ------------------------------------------------------------
|
||||
import os
|
||||
import sys
|
||||
import atexit
|
||||
import logging
|
||||
|
||||
# ----------------------------------------------- Logging Setup ------------------------------------------------------------
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ----------------------------------------------- Main ------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
# Добавляем папку app в Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
||||
from app.api import api
|
||||
from app.heartbeat_monitor import HeartbeatMonitor
|
||||
|
||||
logger.info("🚀 Запуск LLM Agent Backend сервера...")
|
||||
logger.info("📍 API будет доступно на: http://localhost:5000/api")
|
||||
logger.info("📊 Список графов: http://localhost:5000/api/graphs")
|
||||
logger.info("💬 Чат эндпоинт: http://localhost:5000/api/chat")
|
||||
logger.info("\n⚠️ Для остановки нажмите Ctrl+C\n")
|
||||
|
||||
# TODO: Есть проблема с двойным запуском, скорее всего, всего приложения
|
||||
# Если api.debug == True, то проверяем FLASK_RUN_FROM_RELOADER.
|
||||
# Если api.debug == False, то FLASK_RUN_FROM_RELOADER, скорее всего, не будет установлен,
|
||||
# и условие `not api.debug` будет True, что приведет к запуску HeartbeatMonitor.
|
||||
if not api.debug or os.environ.get("FLASK_RUN_FROM_RELOADER") != "true":
|
||||
heartbeat_monitor = HeartbeatMonitor(interval_minutes=5)
|
||||
heartbeat_monitor.start()
|
||||
atexit.register(heartbeat_monitor.stop)
|
||||
logger.info("HeartbeatMonitor запущен.")
|
||||
|
||||
print("🚀 Запуск LLM Agent Backend сервера...")
|
||||
print("📍 API будет доступно на: http://localhost:5000/api")
|
||||
print("📊 Список графов: http://localhost:5000/api/graphs")
|
||||
print("💬 Чат эндпоинт: http://localhost:5000/api/chat")
|
||||
print("\n⚠️ Для остановки нажмите Ctrl+C\n")
|
||||
|
||||
api.run(
|
||||
debug=True,
|
||||
debug=True,
|
||||
port=5000,
|
||||
host='localhost',
|
||||
threaded=True # Включаем многопоточность для обработки параллельных запросов
|
||||
threaded=True
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user