health-check.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. #!/usr/bin/env node
  2. /**
  3. * 健康检查脚本
  4. * 监控交易系统的健康状态并发送告警
  5. */
  6. const fs = require('fs');
  7. const path = require('path');
  8. // 配置
  9. const CONFIG = {
  10. logPath: path.join(__dirname, '../logs/trading.log'),
  11. statsPath: path.join(__dirname, '../logs/stats.json'),
  12. maxLogAge: 5 * 60 * 1000, // 5分钟
  13. minTradingVolume: 0.001, // 最小交易量
  14. maxErrorRate: 0.1, // 10%错误率
  15. alertWebhook: process.env.ALERT_WEBHOOK_URL // Slack/Discord webhook
  16. };
  17. // 健康状态
  18. const HealthStatus = {
  19. HEALTHY: '🟢 健康',
  20. WARNING: '🟡 警告',
  21. CRITICAL: '🔴 危急',
  22. UNKNOWN: '⚫ 未知'
  23. };
  24. /**
  25. * 检查日志文件最后更新时间
  26. */
  27. function checkLogActivity() {
  28. try {
  29. if (!fs.existsSync(CONFIG.logPath)) {
  30. return {
  31. status: HealthStatus.CRITICAL,
  32. message: '日志文件不存在'
  33. };
  34. }
  35. const stats = fs.statSync(CONFIG.logPath);
  36. const lastModified = stats.mtime.getTime();
  37. const now = Date.now();
  38. const age = now - lastModified;
  39. if (age > CONFIG.maxLogAge) {
  40. return {
  41. status: HealthStatus.WARNING,
  42. message: `日志文件${Math.floor(age / 60000)}分钟未更新`
  43. };
  44. }
  45. return {
  46. status: HealthStatus.HEALTHY,
  47. message: '日志更新正常'
  48. };
  49. } catch (error) {
  50. return {
  51. status: HealthStatus.UNKNOWN,
  52. message: `检查日志失败: ${error.message}`
  53. };
  54. }
  55. }
  56. /**
  57. * 检查交易统计
  58. */
  59. function checkTradingStats() {
  60. try {
  61. if (!fs.existsSync(CONFIG.statsPath)) {
  62. return {
  63. status: HealthStatus.WARNING,
  64. message: '统计文件不存在'
  65. };
  66. }
  67. const statsContent = fs.readFileSync(CONFIG.statsPath, 'utf8');
  68. const stats = JSON.parse(statsContent);
  69. // 检查错误率
  70. if (stats.totalOrders > 0) {
  71. const errorRate = stats.failedOrders / stats.totalOrders;
  72. if (errorRate > CONFIG.maxErrorRate) {
  73. return {
  74. status: HealthStatus.CRITICAL,
  75. message: `错误率过高: ${(errorRate * 100).toFixed(1)}%`
  76. };
  77. }
  78. }
  79. // 检查交易量
  80. if (stats.totalVolume < CONFIG.minTradingVolume) {
  81. return {
  82. status: HealthStatus.WARNING,
  83. message: `交易量过低: ${stats.totalVolume} BTC`
  84. };
  85. }
  86. // 检查连续错误
  87. if (stats.consecutiveErrors > 5) {
  88. return {
  89. status: HealthStatus.CRITICAL,
  90. message: `连续错误: ${stats.consecutiveErrors}次`
  91. };
  92. }
  93. return {
  94. status: HealthStatus.HEALTHY,
  95. message: `交易正常 - 成交量: ${stats.totalVolume.toFixed(4)} BTC`
  96. };
  97. } catch (error) {
  98. return {
  99. status: HealthStatus.UNKNOWN,
  100. message: `检查统计失败: ${error.message}`
  101. };
  102. }
  103. }
  104. /**
  105. * 检查进程内存使用
  106. */
  107. function checkMemoryUsage() {
  108. const used = process.memoryUsage();
  109. const heapUsedMB = Math.round(used.heapUsed / 1024 / 1024);
  110. const heapTotalMB = Math.round(used.heapTotal / 1024 / 1024);
  111. const rssMB = Math.round(used.rss / 1024 / 1024);
  112. if (rssMB > 2048) {
  113. return {
  114. status: HealthStatus.CRITICAL,
  115. message: `内存使用过高: ${rssMB}MB`
  116. };
  117. }
  118. if (rssMB > 1024) {
  119. return {
  120. status: HealthStatus.WARNING,
  121. message: `内存使用较高: ${rssMB}MB`
  122. };
  123. }
  124. return {
  125. status: HealthStatus.HEALTHY,
  126. message: `内存使用: ${rssMB}MB (堆: ${heapUsedMB}/${heapTotalMB}MB)`
  127. };
  128. }
  129. /**
  130. * 发送告警
  131. */
  132. async function sendAlert(message) {
  133. if (!CONFIG.alertWebhook) {
  134. console.log('告警Webhook未配置');
  135. return;
  136. }
  137. try {
  138. const fetch = (await import('node-fetch')).default;
  139. await fetch(CONFIG.alertWebhook, {
  140. method: 'POST',
  141. headers: { 'Content-Type': 'application/json' },
  142. body: JSON.stringify({
  143. text: `🚨 交易系统告警\n${message}\n时间: ${new Date().toLocaleString('zh-CN')}`
  144. })
  145. });
  146. } catch (error) {
  147. console.error('发送告警失败:', error.message);
  148. }
  149. }
  150. /**
  151. * 执行健康检查
  152. */
  153. async function performHealthCheck() {
  154. console.log('\n=== 健康检查 ===');
  155. console.log(`时间: ${new Date().toLocaleString('zh-CN')}`);
  156. console.log('');
  157. const checks = [
  158. { name: '日志活动', result: checkLogActivity() },
  159. { name: '交易统计', result: checkTradingStats() },
  160. { name: '内存使用', result: checkMemoryUsage() }
  161. ];
  162. let overallStatus = HealthStatus.HEALTHY;
  163. const criticalIssues = [];
  164. for (const check of checks) {
  165. console.log(`${check.name}: ${check.result.status}`);
  166. console.log(` └─ ${check.result.message}`);
  167. // 记录严重问题
  168. if (check.result.status === HealthStatus.CRITICAL) {
  169. overallStatus = HealthStatus.CRITICAL;
  170. criticalIssues.push(`${check.name}: ${check.result.message}`);
  171. } else if (check.result.status === HealthStatus.WARNING && overallStatus === HealthStatus.HEALTHY) {
  172. overallStatus = HealthStatus.WARNING;
  173. }
  174. }
  175. console.log('\n总体状态:', overallStatus);
  176. // 发送告警
  177. if (overallStatus === HealthStatus.CRITICAL && criticalIssues.length > 0) {
  178. const alertMessage = criticalIssues.join('\n');
  179. console.log('\n⚠️ 发送告警...');
  180. await sendAlert(alertMessage);
  181. }
  182. // 写入健康状态文件
  183. const healthStatus = {
  184. timestamp: new Date().toISOString(),
  185. status: overallStatus,
  186. checks: checks.map(c => ({
  187. name: c.name,
  188. status: c.result.status,
  189. message: c.result.message
  190. }))
  191. };
  192. fs.writeFileSync(
  193. path.join(__dirname, '../logs/health.json'),
  194. JSON.stringify(healthStatus, null, 2)
  195. );
  196. console.log('\n=================\n');
  197. }
  198. // 执行健康检查
  199. performHealthCheck().catch(error => {
  200. console.error('健康检查失败:', error);
  201. process.exit(1);
  202. });