feat: add Uptime Kuma heartbeat monitoring
- Add heartbeat configuration to config.yaml - Implement HeartbeatMonitor class in watch.py - Send heartbeat every 40 seconds to Uptime Kuma push endpoint - Update config.yaml.example with heartbeat settings - Add heartbeat logging at INFO level Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,3 +33,6 @@ config.yaml
|
|||||||
# Excel temp files
|
# Excel temp files
|
||||||
Excel/
|
Excel/
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
.agents/
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
# Excel to SQL Server Migration Configuration
|
# Excel to SQL Server Migration Configuration
|
||||||
|
|
||||||
|
# ================= 定时检查参数 =================
|
||||||
|
check_interval_minutes: 5 # 每隔多少分钟扫描一次源文件
|
||||||
|
|
||||||
|
# ================= 日志配置 =================
|
||||||
|
logging:
|
||||||
|
level: "INFO" # DEBUG / INFO / WARNING
|
||||||
|
log_dir: "logs" # 日志目录(相对于项目根目录)
|
||||||
|
max_keep_days: 7 # 日志文件保留天数
|
||||||
|
|
||||||
|
# ================= Uptime Kuma 心跳检测 =================
|
||||||
|
heartbeat:
|
||||||
|
enabled: false # 是否启用心跳检测
|
||||||
|
interval_seconds: 40 # 心跳发送间隔(秒)
|
||||||
|
url: "YOUR_UPTIME_KUMA_PUSH_URL" # Uptime Kuma 推送端点 URL
|
||||||
|
|
||||||
|
# ================= LAN 源文件路径 =================
|
||||||
|
# key = 本地相对路径 (对应 tables.sources[].file)
|
||||||
|
# value = 局域网 UNC 绝对路径
|
||||||
|
lan_sources:
|
||||||
|
"PG/生产执行卡2022.xlsm": "\\\\192.168.110.113\\生产执行卡\\往年生产执行卡\\生产执行卡2022.xlsm"
|
||||||
|
# ... 其他 LAN 源文件路径
|
||||||
|
|
||||||
# ================= SQL Server 连接 =================
|
# ================= SQL Server 连接 =================
|
||||||
sql_server:
|
sql_server:
|
||||||
driver: "ODBC Driver 18 for SQL Server"
|
driver: "ODBC Driver 18 for SQL Server"
|
||||||
|
|||||||
112
watch.py
112
watch.py
@@ -6,6 +6,9 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from logger import setup_logger
|
from logger import setup_logger
|
||||||
from migrate import load_config, get_connection, create_schema, migrate_table
|
from migrate import load_config, get_connection, create_schema, migrate_table
|
||||||
@@ -14,6 +17,77 @@ from sync import sync_all_files, determine_tables_to_migrate
|
|||||||
logger = logging.getLogger('app')
|
logger = logging.getLogger('app')
|
||||||
|
|
||||||
|
|
||||||
|
class HeartbeatMonitor:
|
||||||
|
"""Uptime Kuma heartbeat monitor running in a separate thread."""
|
||||||
|
|
||||||
|
def __init__(self, url, interval_seconds):
|
||||||
|
self.url = url
|
||||||
|
self.interval_seconds = interval_seconds
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
logger.info(f"心跳监控初始化: 间隔 {interval_seconds} 秒")
|
||||||
|
|
||||||
|
def _send_heartbeat(self):
|
||||||
|
"""Send a heartbeat request to Uptime Kuma."""
|
||||||
|
try:
|
||||||
|
# Add current timestamp to ping parameter
|
||||||
|
ping = str(int(time.time()))
|
||||||
|
full_url = f"{self.url}{ping}"
|
||||||
|
|
||||||
|
request = urllib.request.Request(full_url, method='GET')
|
||||||
|
request.add_header('User-Agent', 'Excel-to-SQL-Server-WatchDaemon/1.0')
|
||||||
|
|
||||||
|
with urllib.request.urlopen(request, timeout=10) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
logger.info(f"心跳发送成功")
|
||||||
|
else:
|
||||||
|
logger.warning(f"心跳返回异常状态码: {response.status}")
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
logger.error(f"心跳发送失败 (网络错误): {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"心跳发送失败 (未知错误): {e}")
|
||||||
|
|
||||||
|
def _run_loop(self):
|
||||||
|
"""Main heartbeat loop running in the thread."""
|
||||||
|
logger.info("心跳监控线程已启动")
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
self._send_heartbeat()
|
||||||
|
|
||||||
|
# Wait for the interval or until stopped
|
||||||
|
for _ in range(self.interval_seconds):
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
logger.info("心跳监控线程已停止")
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start the heartbeat monitor thread."""
|
||||||
|
if self.running:
|
||||||
|
logger.warning("心跳监控已在运行")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._run_loop, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
logger.info("心跳监控已启动")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop the heartbeat monitor thread."""
|
||||||
|
if not self.running:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("正在停止心跳监控...")
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# Wait for thread to finish
|
||||||
|
if self.thread and self.thread.is_alive():
|
||||||
|
self.thread.join(timeout=5)
|
||||||
|
|
||||||
|
logger.info("心跳监控已停止")
|
||||||
|
|
||||||
|
|
||||||
def run_once(cfg):
|
def run_once(cfg):
|
||||||
"""Execute one sync + migrate cycle."""
|
"""Execute one sync + migrate cycle."""
|
||||||
excel_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Excel')
|
excel_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Excel')
|
||||||
@@ -100,18 +174,36 @@ def main():
|
|||||||
logger.info(f" 同步间隔: {interval} 分钟")
|
logger.info(f" 同步间隔: {interval} 分钟")
|
||||||
logger.info("=" * 50)
|
logger.info("=" * 50)
|
||||||
|
|
||||||
while True:
|
# Initialize heartbeat monitor if configured
|
||||||
try:
|
heartbeat_monitor = None
|
||||||
run_once(cfg)
|
heartbeat_cfg = cfg.get('heartbeat', {})
|
||||||
except Exception as e:
|
if heartbeat_cfg.get('enabled', False):
|
||||||
logger.error(f"运行周期异常: {e}", exc_info=True)
|
url = heartbeat_cfg.get('url', '').strip()
|
||||||
|
interval_seconds = heartbeat_cfg.get('interval_seconds', 40)
|
||||||
|
|
||||||
if args.once:
|
if url:
|
||||||
logger.info("--once 模式,退出")
|
heartbeat_monitor = HeartbeatMonitor(url, interval_seconds)
|
||||||
break
|
heartbeat_monitor.start()
|
||||||
|
else:
|
||||||
|
logger.warning("心跳已启用但 URL 未配置,跳过心跳监控")
|
||||||
|
|
||||||
logger.info(f"下次检查: {interval} 分钟后")
|
try:
|
||||||
time.sleep(interval * 60)
|
while True:
|
||||||
|
try:
|
||||||
|
run_once(cfg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"运行周期异常: {e}", exc_info=True)
|
||||||
|
|
||||||
|
if args.once:
|
||||||
|
logger.info("--once 模式,退出")
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"下次检查: {interval} 分钟后")
|
||||||
|
time.sleep(interval * 60)
|
||||||
|
finally:
|
||||||
|
# Stop heartbeat monitor on exit
|
||||||
|
if heartbeat_monitor:
|
||||||
|
heartbeat_monitor.stop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user