Files
panel/internal/app/health.go
T
耗子andClaude Opus 4.7 f024381660 feat(panel): 数据库故障降级、健康告警与检测
- 网站统计聚合器 flush 失败也 commit(丢弃已 drain 增量),防止 DB
  长期不可写时内存无界累积导致 OOM/CPU 100%
- 势态感知同样在 upsert 失败时上报健康问题
- 新增 internal/app.Health 全局健康状态注册表,通过 /home/health 暴露
- 前端 layout 顶部新增 HealthBanner,DB 故障时红色横幅提醒并给出修复引导
- acepanel fix 检测数据库损坏改用 PRAGMA quick_check,能识别 malformed
  等仅在查询时才暴露的静默损坏
- 主库与辅助库 DSN 一律设为 synchronous(FULL),减少宿主 write-back
  缓存丢失 fsync 导致的损坏概率
- 移除 debug.SetMemoryLimit(256MB):软上限在活对象超限时会驱动 GC 反复
  追赶不可回收对象,反而放大 CPU 打满

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 04:21:10 +08:00

74 lines
1.8 KiB
Go

package app
import (
"sort"
"sync"
"time"
)
// HealthLevelError 需要用户立即处理
const HealthLevelError = "error"
// HealthLevelWarning 提示性问题,通常已被系统自动降级处理
const HealthLevelWarning = "warning"
// HealthIssue 单条健康问题
// Key 为稳定标识符(如 database:stat),前端据此选择翻译文案
// Message 为原始错误详情,供诊断参考
type HealthIssue struct {
Key string `json:"key"`
Level string `json:"level"`
Message string `json:"message"`
Since time.Time `json:"since"`
}
type healthRegistry struct {
mu sync.RWMutex
issues map[string]HealthIssue
}
// Health 全局健康状态注册表,供各后台任务上报/清除故障
var Health = &healthRegistry{issues: make(map[string]HealthIssue)}
// Report 上报或更新一条健康问题
// 同 key 重复上报时保留首次上报时间,仅更新 message 和 level
func (h *healthRegistry) Report(key, level, message string) {
h.mu.Lock()
defer h.mu.Unlock()
if existing, ok := h.issues[key]; ok {
existing.Level = level
existing.Message = message
h.issues[key] = existing
return
}
h.issues[key] = HealthIssue{
Key: key,
Level: level,
Message: message,
Since: time.Now(),
}
}
// Clear 清除指定 key 的健康问题
func (h *healthRegistry) Clear(key string) {
h.mu.Lock()
defer h.mu.Unlock()
delete(h.issues, key)
}
// Snapshot 返回当前所有健康问题,按 Since 升序(越早发生越靠前)
func (h *healthRegistry) Snapshot() []HealthIssue {
h.mu.RLock()
defer h.mu.RUnlock()
result := make([]HealthIssue, 0, len(h.issues))
for _, issue := range h.issues {
result = append(result, issue)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Since.Before(result[j].Since)
})
return result
}