Files
Nexus/server/background/self_monitor.py
T
Your Name 32feb1b6db fix: 全站 ruff 清零 — 77 errors → 0
Fixes:
- F821: install.py site_url 未定义(NameError)、sync_v2.py os 未导入、script_jobs.py LOG f-string
- F401: 移除 22 个未使用导入(models/__init__.py、install.py、auth.py 等)
- B904: 20 个 except 块 raise 添加 from e/from None
- S110: 20 个有意的 try-except-pass 添加 noqa 注释(SSH清理/DDL幂等/WS断连)
- B007: 3 个未使用循环变量重命名(dirs→_dirs、server_id→_server_id)
- F541: 3 个无占位符 f-string 修正
- F841: auth.py 未使用 ip_address 变量移除
- S105: auth_service.py Redis key prefix 误报 noqa
- B023: script_execution_flush.py 循环变量绑定修复

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:07:45 +08:00

108 lines
4.2 KiB
Python

"""Nexus — Python Self-Monitor Background Task
Every 30 seconds: check Redis, MySQL, WebSocket connections.
If critical service fails → send Telegram system alert.
If service recovers → send Telegram recovery notification.
"""
import asyncio
import logging
import time
from sqlalchemy import text
from server.infrastructure.database.session import AsyncSessionLocal
from server.infrastructure.redis.client import get_redis
from server.infrastructure.telegram import send_telegram_system_alert
from server.api.websocket import manager as ws_manager
from server.config import settings
logger = logging.getLogger("nexus.self_monitor")
MONITOR_INTERVAL = 30 # seconds
_TELEGRAM_COOLDOWN = 300 # 5 minutes — same alert won't re-push Telegram
# Track previous state for recovery detection
_prev_redis_ok = True
_prev_mysql_ok = True
# Track last Telegram push time per alert key (prevent spam)
_last_telegram_time: dict = {}
async def self_monitor_loop():
"""Background task: every 30s, self-check critical services.
Checks:
1. Redis connection — if lost, send Telegram warning; if recovered, send recovery
2. MySQL connection — if lost, send Telegram warning; if recovered, send recovery
3. WebSocket client count (via ConnectionManager)
"""
global _prev_redis_ok, _prev_mysql_ok
def _should_send(key: str) -> bool:
"""Rate-limit Telegram pushes to 1 per 5 minutes per alert key."""
now = time.monotonic()
last = _last_telegram_time.get(key, 0)
if now - last < _TELEGRAM_COOLDOWN:
return False
_last_telegram_time[key] = now
return True
logger.info("Self-monitor loop started (interval: 30s)")
while True:
await asyncio.sleep(MONITOR_INTERVAL)
# ── Redis check ──
redis_ok = True
try:
redis = get_redis()
await redis.ping()
except Exception:
redis_ok = False
logger.warning("Redis health check failed", exc_info=True)
try:
if settings.REDIS_URL and _should_send("NOTIFY_SYSTEM_REDIS"):
await send_telegram_system_alert("⚠️ Redis连接丢失,心跳数据可能中断", notify_key="NOTIFY_SYSTEM_REDIS")
except Exception:
logger.error("Failed to send Redis alert via Telegram")
# Redis recovery notification
if redis_ok and not _prev_redis_ok:
try:
await send_telegram_system_alert("🟢 Redis连接已恢复正常", notify_key="NOTIFY_SYSTEM_REDIS")
_last_telegram_time.pop("NOTIFY_SYSTEM_REDIS", None) # clear cooldown
except Exception:
logger.error("Failed to send Redis recovery via Telegram")
logger.info("Redis connection recovered")
_prev_redis_ok = redis_ok
# ── MySQL check ──
mysql_ok = True
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
except Exception:
mysql_ok = False
logger.warning("MySQL health check failed", exc_info=True)
try:
if _should_send("NOTIFY_SYSTEM_MYSQL"):
await send_telegram_system_alert("🔴 MySQL 连接异常,请检查数据库服务", notify_key="NOTIFY_SYSTEM_MYSQL")
except Exception:
logger.error("Failed to send MySQL alert via Telegram", exc_info=True)
# MySQL recovery notification
if mysql_ok and not _prev_mysql_ok:
try:
await send_telegram_system_alert("🟢 MySQL连接已恢复正常", notify_key="NOTIFY_SYSTEM_MYSQL")
_last_telegram_time.pop("NOTIFY_SYSTEM_MYSQL", None) # clear cooldown
except Exception:
logger.error("Failed to send MySQL recovery via Telegram")
logger.info("MySQL connection recovered")
_prev_mysql_ok = mysql_ok
# ── Log status ──
logger.debug(
f"Self-monitor: redis={'ok' if redis_ok else 'error'}, "
f"mysql={'ok' if mysql_ok else 'error'}, "
f"ws_clients={ws_manager.client_count}"
)