123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- #!/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);
- });
|