shared/lib/alert/alert-manager.ts

98 lines
3.1 KiB
TypeScript

// shared/lib/alert/alert-manager.ts — 告警管理引擎
import type { AlertLevel, AlertChannelConfig, AlertDecision } from './types'
export interface CooldownProvider {
getLastAlertTime(channelId: number, serviceId: number, level: string): Promise<number | null>
setLastAlertTime(channelId: number, serviceId: number, level: string, timestamp: number): Promise<void>
}
export interface AlertPushChannel {
push(title: string, content: string, level: string): Promise<{ success: boolean }>
}
export class AlertManager {
private cooldownProvider: CooldownProvider
constructor(cooldownProvider: CooldownProvider) {
this.cooldownProvider = cooldownProvider
}
// 评估是否向某 channel 推送告警
async evaluate(
channel: AlertChannelConfig,
serviceId: number,
level: AlertLevel,
now: Date = new Date()
): Promise<AlertDecision> {
// 1. 级别匹配
if (!this.levelMatches(channel, level)) {
return { shouldPush: false, reason: `level ${level} not enabled for channel ${channel.name}` }
}
// 2. 免打扰时段
if (this.isQuietPeriod(channel, now) && !(level === 'critical' && channel.quietBypassCritical)) {
return { shouldPush: false, reason: `quiet period for channel ${channel.name}` }
}
// 3. 冷却检查
const cooling = await this.isCoolingDown(channel.id, serviceId, level, channel.cooldownMinutes, now)
if (cooling) {
return { shouldPush: false, reason: `cooldown for channel ${channel.name}` }
}
return { shouldPush: true }
}
// 级别匹配检查
private levelMatches(channel: AlertChannelConfig, level: AlertLevel): boolean {
switch (level) {
case 'critical': return channel.levelCritical
case 'warning': return channel.levelWarning
case 'info': return channel.levelInfo
}
}
// 免打扰时段判定(支持跨日时段)
isQuietPeriod(channel: AlertChannelConfig, now: Date): boolean {
if (!channel.quietEnabled) return false
const [sh, sm] = channel.quietStart.split(':').map(Number)
const [eh, em] = channel.quietEnd.split(':').map(Number)
const current = now.getHours() * 60 + now.getMinutes()
const start = sh * 60 + sm
const end = eh * 60 + em
if (start <= end) {
// 同日时段,如 02:00 - 06:00
return current >= start && current < end
} else {
// 跨日时段,如 23:00 - 07:00
return current >= start || current < end
}
}
// 冷却检查
async isCoolingDown(
channelId: number,
serviceId: number,
level: AlertLevel,
cooldownMinutes: number,
now: Date = new Date()
): Promise<boolean> {
const lastTime = await this.cooldownProvider.getLastAlertTime(channelId, serviceId, level)
if (!lastTime) return false
const elapsed = now.getTime() - lastTime
return elapsed < cooldownMinutes * 60 * 1000
}
// 记录告警时间
async recordAlert(
channelId: number,
serviceId: number,
level: AlertLevel,
timestamp: number = Date.now()
): Promise<void> {
await this.cooldownProvider.setLastAlertTime(channelId, serviceId, level, timestamp)
}
}