71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package notify
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// alertState 记录告警节流信息。
|
|
//
|
|
// 为什么必须落盘:进程检测到挂载失效后会主动退出、由 restart 策略拉起。
|
|
// 若节流时间只存在内存里,重启后 lastAlert 归零,会立刻再次告警 ——
|
|
// 持续故障就变成「每轮重启都发一条飞书」,把真正的告警淹成骚扰。
|
|
type alertState struct {
|
|
// LastDegradedAlert 是上次发送「归档目录不可用」告警的时间。
|
|
LastDegradedAlert time.Time `json:"lastDegradedAlert,omitempty"`
|
|
// LastDegradedReason 用于在故障原因变化时立即重新告警。
|
|
LastDegradedReason string `json:"lastDegradedReason,omitempty"`
|
|
// ExitCount 统计因挂载失效而退出的累计次数,用于识别「反复重启」。
|
|
ExitCount int `json:"exitCount,omitempty"`
|
|
}
|
|
|
|
func alertStatePath(stateDir string) string {
|
|
return filepath.Join(stateDir, "alert-state.json")
|
|
}
|
|
|
|
func loadAlertState(stateDir string) alertState {
|
|
var st alertState
|
|
if stateDir == "" {
|
|
return st
|
|
}
|
|
body, err := os.ReadFile(alertStatePath(stateDir))
|
|
if err != nil {
|
|
return st
|
|
}
|
|
_ = json.Unmarshal(body, &st)
|
|
return st
|
|
}
|
|
|
|
func saveAlertState(stateDir string, st alertState) error {
|
|
if stateDir == "" {
|
|
return nil
|
|
}
|
|
path := alertStatePath(stateDir)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
return err
|
|
}
|
|
body, err := json.MarshalIndent(st, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, body, 0o600); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
// shouldAlert 判断是否该发告警:间隔到了,或故障原因发生变化。
|
|
func (st alertState) shouldAlert(reason string, now time.Time, interval time.Duration) bool {
|
|
if st.LastDegradedAlert.IsZero() {
|
|
return true
|
|
}
|
|
if reason != st.LastDegradedReason {
|
|
// 原因变了(例如从「目录不存在」变成「不可读」)值得立刻提醒。
|
|
return true
|
|
}
|
|
return now.Sub(st.LastDegradedAlert) >= interval
|
|
}
|