mirror of
https://github.com/tnb-labs/panel.git
synced 2026-09-21 05:11:14 +08:00
- 网站统计聚合器 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>
74 lines
1.8 KiB
Go
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
|
|
}
|