"use strict"; /** * AlertStateMonitor(D23:每 5min 定时扫活跃告警) * * 设计: * - 现有 alertAggregationEngine 是"事件驱动"(来一条新告警检查一次), * 不会主动扫存量告警的超时情况 * - AlertStateMonitor 每 5min 跑一次,检查: * ① 告警挂了多久 → 超 escalation_fallback.auto_escalate_minutes → 自动升级 * ② remediation.status === "running" 超时 → 暂停处置 + 通知值班员 * ③ remediation.status === "completed" → 检查 auto_close_after_remediation_recovery */ const _CHECK_INTERVAL_MS = 5 * 60 * 1000; // 5 分钟 const _REMEDIATION_TIMEOUT_MS = 30 * 60 * 1000; // 30 分钟硬阈值 let _timer = null; let _isRunning = false; /** * 启动定时监控(应在巡检模块初始化时调用一次) * @param {object} deps - 依赖注入 * @param {function} deps.readFn - 存储读取函数 * @param {function} deps.writeFn - 存储写入函数 * @param {object} deps.alertRecordStore - alertRecordStore 模块 * @param {object} deps.aggregationEngine - aggregationEngine 模块 * @param {object} deps.remediationEngine - remediationEngine 模块 * @param {function} [deps.onNotification] - 通知回调 */ function start(deps) { if (_timer) return; // 已启动 const { readFn, writeFn, alertRecordStore, aggregationEngine, remediationEngine, onNotification } = deps; async function run() { if (_isRunning) return; // 避免重叠 _isRunning = true; try { const activeAlerts = alertRecordStore.listAlerts(readFn, { limit: 500 }); const eligibleAlerts = activeAlerts.filter( (a) => a.status === "active" || a.status === "acknowledged" ); for (const alert of eligibleAlerts) { await _checkOne(alert, { readFn, writeFn, aggregationEngine, remediationEngine, alertRecordStore, onNotification }); } } catch (e) { console.error("[AlertStateMonitor] run error:", e.message); } finally { _isRunning = false; } } _timer = setInterval(run, _CHECK_INTERVAL_MS); console.info("[AlertStateMonitor] 已启动,每 5 分钟检查一次活跃告警"); // 立即跑一次 run(); } function stop() { if (_timer) { clearInterval(_timer); _timer = null; console.info("[AlertStateMonitor] 已停止"); } } function isRunning() { return !!_timer; } async function _checkOne(alert, deps) { const { readFn, writeFn, aggregationEngine, remediationEngine, alertRecordStore, onNotification } = deps; const snapshot = alert.matched_plan_snapshot; if (!snapshot) return; // 没匹配预案的不处理 const now = Date.now(); const elapsedMin = (now - alert.triggered_at) / 60000; // ── 检查 1:escalation_fallback.auto_escalate_minutes ── const fallback = snapshot.escalation_fallback; if (fallback?.auto_escalate_minutes && elapsedMin >= fallback.auto_escalate_minutes) { // 避免重复升级:同一条告警只升级一次 const alreadyEscalated = (alert.audit_log || []).some( (a) => a.action === "escalation_fallback_timeout" || a.action === "aggregation_escalation" ); if (!alreadyEscalated) { console.warn(`[AlertStateMonitor] 告警 ${alert.id} 挂了 ${elapsedMin.toFixed(0)}min,触发自动升级`); aggregationEngine.escalateAlert(readFn, writeFn, "escalation_fallback_timeout", { source_alert_ids: [alert.id], target_level: fallback.escalation_target_level || alert.level, message: `告警挂 ${elapsedMin.toFixed(0)}min 未处置,自动升级`, }); // 追加审计 _appendAudit(alertRecordStore, readFn, writeFn, alert.id, { ts: now, actor: "alertStateMonitor", action: "escalation_fallback_timeout", detail: `elapsed=${elapsedMin.toFixed(0)}min`, }); onNotification?.({ type: "escalation", alertId: alert.id }); } } // ── 检查 2:remediation 运行超时 ── const remediation = alert.remediation; if (remediation?.status === "running") { const runningFor = now - (remediation.started_at || 0); if (runningFor > _REMEDIATION_TIMEOUT_MS) { console.warn(`[AlertStateMonitor] 告警 ${alert.id} 处置运行 ${(runningFor / 60000).toFixed(0)}min,超时暂停`); remediationEngine.pause(alert.id); _appendAudit(alertRecordStore, readFn, writeFn, alert.id, { ts: now, actor: "alertStateMonitor", action: "remediation_timeout", detail: `running=${(runningFor / 60000).toFixed(0)}min,自动暂停`, }); onNotification?.({ type: "remediation_timeout", alertId: alert.id }); } } } function _appendAudit(alertRecordStore, readFn, writeFn, alertId, auditEntry) { try { // 直接读写底层存储(避免 alertRecordStore 暴露内部方法) const raw = readFn("netcatty_inspection_alert_records_v1"); const alerts = raw ? (typeof raw === "string" ? JSON.parse(raw) : raw) : []; const idx = alerts.findIndex((a) => a.id === alertId); if (idx >= 0) { alerts[idx].audit_log = [...(alerts[idx].audit_log || []), auditEntry]; writeFn("netcatty_inspection_alert_records_v1", JSON.stringify(alerts)); } } catch (e) { console.warn("[AlertStateMonitor] 追加审计失败:", e.message); } } function getStatus() { return { running: isRunning(), interval_minutes: _CHECK_INTERVAL_MS / 60000, remediation_timeout_minutes: _REMEDIATION_TIMEOUT_MS / 60000, }; } module.exports = { start, stop, isRunning, getStatus, // 测试用 _CHECK_INTERVAL_MS, _REMEDIATION_TIMEOUT_MS, };