#!/usr/bin/env node /** * 健康检查脚本 * 监控交易系统的健康状态并发送告警 */ const fs = require('fs'); const path = require('path'); // 配置 const CONFIG = { logPath: path.join(__dirname, '../logs/trading.log'), statsPath: path.join(__dirname, '../logs/stats.json'), maxLogAge: 5 * 60 * 1000, // 5分钟 minTradingVolume: 0.001, // 最小交易量 maxErrorRate: 0.1, // 10%错误率 alertWebhook: process.env.ALERT_WEBHOOK_URL // Slack/Discord webhook }; // 健康状态 const HealthStatus = { HEALTHY: '🟢 健康', WARNING: '🟡 警告', CRITICAL: '🔴 危急', UNKNOWN: '⚫ 未知' }; /** * 检查日志文件最后更新时间 */ function checkLogActivity() { try { if (!fs.existsSync(CONFIG.logPath)) { return { status: HealthStatus.CRITICAL, message: '日志文件不存在' }; } const stats = fs.statSync(CONFIG.logPath); const lastModified = stats.mtime.getTime(); const now = Date.now(); const age = now - lastModified; if (age > CONFIG.maxLogAge) { return { status: HealthStatus.WARNING, message: `日志文件${Math.floor(age / 60000)}分钟未更新` }; } return { status: HealthStatus.HEALTHY, message: '日志更新正常' }; } catch (error) { return { status: HealthStatus.UNKNOWN, message: `检查日志失败: ${error.message}` }; } } /** * 检查交易统计 */ function checkTradingStats() { try { if (!fs.existsSync(CONFIG.statsPath)) { return { status: HealthStatus.WARNING, message: '统计文件不存在' }; } const statsContent = fs.readFileSync(CONFIG.statsPath, 'utf8'); const stats = JSON.parse(statsContent); // 检查错误率 if (stats.totalOrders > 0) { const errorRate = stats.failedOrders / stats.totalOrders; if (errorRate > CONFIG.maxErrorRate) { return { status: HealthStatus.CRITICAL, message: `错误率过高: ${(errorRate * 100).toFixed(1)}%` }; } } // 检查交易量 if (stats.totalVolume < CONFIG.minTradingVolume) { return { status: HealthStatus.WARNING, message: `交易量过低: ${stats.totalVolume} BTC` }; } // 检查连续错误 if (stats.consecutiveErrors > 5) { return { status: HealthStatus.CRITICAL, message: `连续错误: ${stats.consecutiveErrors}次` }; } return { status: HealthStatus.HEALTHY, message: `交易正常 - 成交量: ${stats.totalVolume.toFixed(4)} BTC` }; } catch (error) { return { status: HealthStatus.UNKNOWN, message: `检查统计失败: ${error.message}` }; } } /** * 检查进程内存使用 */ function checkMemoryUsage() { const used = process.memoryUsage(); const heapUsedMB = Math.round(used.heapUsed / 1024 / 1024); const heapTotalMB = Math.round(used.heapTotal / 1024 / 1024); const rssMB = Math.round(used.rss / 1024 / 1024); if (rssMB > 2048) { return { status: HealthStatus.CRITICAL, message: `内存使用过高: ${rssMB}MB` }; } if (rssMB > 1024) { return { status: HealthStatus.WARNING, message: `内存使用较高: ${rssMB}MB` }; } return { status: HealthStatus.HEALTHY, message: `内存使用: ${rssMB}MB (堆: ${heapUsedMB}/${heapTotalMB}MB)` }; } /** * 发送告警 */ async function sendAlert(message) { if (!CONFIG.alertWebhook) { console.log('告警Webhook未配置'); return; } try { const fetch = (await import('node-fetch')).default; await fetch(CONFIG.alertWebhook, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `🚨 交易系统告警\n${message}\n时间: ${new Date().toLocaleString('zh-CN')}` }) }); } catch (error) { console.error('发送告警失败:', error.message); } } /** * 执行健康检查 */ async function performHealthCheck() { console.log('\n=== 健康检查 ==='); console.log(`时间: ${new Date().toLocaleString('zh-CN')}`); console.log(''); const checks = [ { name: '日志活动', result: checkLogActivity() }, { name: '交易统计', result: checkTradingStats() }, { name: '内存使用', result: checkMemoryUsage() } ]; let overallStatus = HealthStatus.HEALTHY; const criticalIssues = []; for (const check of checks) { console.log(`${check.name}: ${check.result.status}`); console.log(` └─ ${check.result.message}`); // 记录严重问题 if (check.result.status === HealthStatus.CRITICAL) { overallStatus = HealthStatus.CRITICAL; criticalIssues.push(`${check.name}: ${check.result.message}`); } else if (check.result.status === HealthStatus.WARNING && overallStatus === HealthStatus.HEALTHY) { overallStatus = HealthStatus.WARNING; } } console.log('\n总体状态:', overallStatus); // 发送告警 if (overallStatus === HealthStatus.CRITICAL && criticalIssues.length > 0) { const alertMessage = criticalIssues.join('\n'); console.log('\n⚠️ 发送告警...'); await sendAlert(alertMessage); } // 写入健康状态文件 const healthStatus = { timestamp: new Date().toISOString(), status: overallStatus, checks: checks.map(c => ({ name: c.name, status: c.result.status, message: c.result.message })) }; fs.writeFileSync( path.join(__dirname, '../logs/health.json'), JSON.stringify(healthStatus, null, 2) ); console.log('\n=================\n'); } // 执行健康检查 performHealthCheck().catch(error => { console.error('健康检查失败:', error); process.exit(1); });