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
|
||||||
30
run.py
30
run.py
|
|
@ -1,22 +1,38 @@
|
||||||
# ----------------------------------------------- Imports ------------------------------------------------------------
|
# ----------------------------------------------- Imports ------------------------------------------------------------
|
||||||
import os
|
import os
|
||||||
import sys
|
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 ------------------------------------------------------------
|
# ----------------------------------------------- Main ------------------------------------------------------------
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Добавляем папку app в Python path
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
||||||
from app.api import api
|
from app.api import api
|
||||||
|
from app.heartbeat_monitor import HeartbeatMonitor
|
||||||
|
|
||||||
print("🚀 Запуск LLM Agent Backend сервера...")
|
logger.info("🚀 Запуск LLM Agent Backend сервера...")
|
||||||
print("📍 API будет доступно на: http://localhost:5000/api")
|
logger.info("📍 API будет доступно на: http://localhost:5000/api")
|
||||||
print("📊 Список графов: http://localhost:5000/api/graphs")
|
logger.info("📊 Список графов: http://localhost:5000/api/graphs")
|
||||||
print("💬 Чат эндпоинт: http://localhost:5000/api/chat")
|
logger.info("💬 Чат эндпоинт: http://localhost:5000/api/chat")
|
||||||
print("\n⚠️ Для остановки нажмите Ctrl+C\n")
|
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 запущен.")
|
||||||
|
|
||||||
api.run(
|
api.run(
|
||||||
debug=True,
|
debug=True,
|
||||||
port=5000,
|
port=5000,
|
||||||
host='localhost',
|
host='localhost',
|
||||||
threaded=True # Включаем многопоточность для обработки параллельных запросов
|
threaded=True
|
||||||
)
|
)
|
||||||
Loading…
Reference in New Issue
Block a user