Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
- 8 new store modules + matcher + initialResponse + aggregation + remediation + AlertStateMonitor + planGenerator - alert chain auto-integrated after runInspection: freeze snapshot + generate response + real-time agg + auto-start monitor - AlertAIPanel component + MonitoringDetailPanel expandable alert list - PlanManagementPage: full page with list/edit/AI generate/templates/commands
152 lines
5.5 KiB
JavaScript
152 lines
5.5 KiB
JavaScript
"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,
|
||
};
|