142 lines
4.7 KiB
Python
142 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
agents_health_check.py — MoFin Tier1 快速健康检查
|
|
====================================================
|
|
每 5 分钟运行一次(crontab)。检查关键服务的端口/HTTP/DB 可用性。
|
|
全正常时静默(不输出)。异常时写入 TODO 文件和 JSON 报告。
|
|
|
|
部署: crontab */5 * * * * cd /home/hmo/MoFin && python3 agents_health_check.py
|
|
"""
|
|
import json, os, sys, socket, sqlite3, urllib.request
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
# ---- Config ----
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
TEMP_DIR = SCRIPT_DIR / "gateway" / "temp"
|
|
LOGS_DIR = SCRIPT_DIR / "gateway" / "logs"
|
|
|
|
# Ensure dirs
|
|
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
|
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
REPORT_FILE = TEMP_DIR / "last_health_check.json"
|
|
TODO_FILE = TEMP_DIR / "health_todos.jsonl"
|
|
LOG_FILE = LOGS_DIR / "health_check.log"
|
|
|
|
# ---- Service List ----
|
|
SERVICES = [
|
|
{"name": "mofin_api", "label": "MoFin API", "host": "127.0.0.1", "port": 8899, "type": "http", "check": "/api/portfolio"},
|
|
{"name": "mofin_dashboard", "label": "Dashboard", "host": "127.0.0.1", "port": 5804, "type": "http", "check": "/api/health"},
|
|
{"name": "zhiwei_gateway", "label": "知微 Gateway", "host": "127.0.0.1", "port": 8643, "type": "http", "check": "/v1/health"},
|
|
{"name": "ejabberd", "label": "ejabberd XMPP", "host": "127.0.0.1", "port": 5222, "type": "tcp", "check": None},
|
|
{"name": "mofin_db", "label": "MoFin 数据库", "host": "127.0.0.1", "port": 0, "type": "db", "check": "/home/hmo/web-dashboard/data/mofin.db"},
|
|
]
|
|
|
|
|
|
# ---- Checkers ----
|
|
|
|
def check_tcp(host, port, timeout=3):
|
|
try:
|
|
sock = socket.create_connection((host, port), timeout=timeout)
|
|
sock.close()
|
|
return True, "ok"
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
|
|
def check_http(host, port, path, timeout=3):
|
|
try:
|
|
url = f"http://{host}:{port}{path}"
|
|
req = urllib.request.Request(url)
|
|
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
return 200 <= resp.status < 300, f"HTTP {resp.status}"
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
|
|
def check_db(db_path):
|
|
try:
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("SELECT 1")
|
|
conn.close()
|
|
return True, "ok"
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
|
|
# ---- Main ----
|
|
|
|
def run():
|
|
now = datetime.now()
|
|
results = []
|
|
issues = []
|
|
|
|
for svc in SERVICES:
|
|
if svc["type"] == "tcp":
|
|
ok, detail = check_tcp(svc["host"], svc["port"])
|
|
elif svc["type"] == "http":
|
|
ok, detail = check_http(svc["host"], svc["port"], svc["check"])
|
|
elif svc["type"] == "db":
|
|
ok, detail = check_db(svc["check"])
|
|
else:
|
|
ok, detail = False, "unknown type"
|
|
|
|
results.append({
|
|
"name": svc["name"],
|
|
"label": svc["label"],
|
|
"type": svc["type"],
|
|
"port": svc["port"],
|
|
"health": {"ok": ok},
|
|
"detail": detail,
|
|
})
|
|
|
|
if not ok:
|
|
issues.append(svc)
|
|
|
|
# Write report
|
|
report = {
|
|
"services": results,
|
|
"summary": {
|
|
"ok": sum(1 for r in results if r["health"]["ok"]),
|
|
"total": len(results),
|
|
},
|
|
"generated_at": now.strftime("%Y-%m-%d %H:%M:%S"),
|
|
}
|
|
with open(REPORT_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(report, f, ensure_ascii=False, indent=2)
|
|
|
|
# Handle issues
|
|
if issues:
|
|
# Write TODO entries
|
|
with open(TODO_FILE, "a", encoding="utf-8") as f:
|
|
for svc in issues:
|
|
entry = {
|
|
"service": svc["name"],
|
|
"label": svc["label"],
|
|
"reason": next((r["detail"] for r in results if r["name"] == svc["name"]), "unknown"),
|
|
"timestamp": now.isoformat(),
|
|
}
|
|
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
|
|
# Log to file
|
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
|
f.write(f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] ISSUES: {len(issues)} failed\n")
|
|
for svc in issues:
|
|
detail = next((r["detail"] for r in results if r["name"] == svc["name"]), "")
|
|
f.write(f" - {svc['label']}: {detail}\n")
|
|
|
|
# Print to stdout (visible in cron log)
|
|
print(f"[{now.strftime('%H:%M')}] Health check: {len(issues)}/{len(SERVICES)} services failed")
|
|
for svc in issues:
|
|
detail = next((r["detail"] for r in results if r["name"] == svc["name"]), "")
|
|
print(f" FAIL: {svc['label']} ({svc['name']}) — {detail}")
|
|
else:
|
|
# All OK → silent
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|