diff --git a/backend/repository/db.go b/backend/repository/db.go
index 9876527..d2acfdb 100644
--- a/backend/repository/db.go
+++ b/backend/repository/db.go
@@ -114,6 +114,22 @@ func (d *JsonDB) raiseLegacyAlertThresholds() {
}
}
+// expandDatabaseErrorPattern 把内置「数据库连接失败」规则升级为覆盖
+// MySQL / PostgreSQL / Redis 的主流报错文案。
+//
+// 只替换"仍是旧默认查询"的规则,不覆盖用户自己改过的。
+func (d *JsonDB) expandDatabaseErrorPattern() {
+ const oldQuery = `{namespace=~".+"} |~ "(?i)(connection refused|connection reset by peer|too many connections)"`
+ // 见 seed/alert_rules.go 里的同名规则,两处必须保持一致
+ const newQuery = `{namespace=~".+"} |~ "(?i)(connection refused|connection reset by peer|broken pipe|no route to host|cannot assign requested address|connection timed out|i/o timeout|too many connections|too many clients already|max number of clients reached|connection pool|connection is not available|could not create connection|communications link failure|server closed the connection unexpectedly|unexpected EOF|driver: bad connection|FATAL:\\s*(password authentication failed|sorry, too many clients|terminating connection)|NOAUTH|WRONGPASS|READONLY you can.t write|CLUSTERDOWN)"`
+ for i := range d.AlertRules {
+ if d.AlertRules[i].Query == oldQuery {
+ log.Printf("[schema] 告警规则 %q 扩展数据库报错匹配模式", d.AlertRules[i].Name)
+ d.AlertRules[i].Query = newQuery
+ }
+ }
+}
+
// normalizeDefaultChannelName 把默认通道的名字统一成「默认告警」。
//
// 只改名,不动 Webhook 与 enabled:首次部署的占位通道必须保持"空地址 + 禁用",
@@ -430,7 +446,7 @@ func (d *JsonDB) dedupeLogSourceIDs() {
//
// v0/v1(无字段)→ 1:新增 auditLogs / schemaVersion(老库走零值兜底即可,无需处理)
// v1 → 2:预留示例(如把 ScrapeJobs 从 logPath 拆分时,需按旧结构生成默认 job)
-const CurrentSchemaVersion = 9
+const CurrentSchemaVersion = 10
// migrateSchema 按版本号顺序迁移老库。返回是否发生了迁移(需要回写)。
func (d *JsonDB) migrateSchema() bool {
@@ -507,6 +523,12 @@ func (d *JsonDB) migrateSchema() bool {
// 这里只改名字,不动 Webhook、不动启用状态。
d.normalizeDefaultChannelName()
d.SchemaVersion = 9
+ case 9:
+ // v9 → v10:内置「数据库连接失败」规则扩展报错模式
+ // (原来只匹配 3 个词,漏掉 PG 的 FATAL: too many clients、
+ // Redis 的 NOAUTH / WRONGPASS / connection pool timeout 等常见报错)
+ d.expandDatabaseErrorPattern()
+ d.SchemaVersion = 10
default:
// 防御:出现未知版本(比当前代码还新)时不乱动,避免降级把数据写坏
log.Printf("[schema] 数据库版本 %d 高于当前代码支持版本 %d,跳过迁移", d.SchemaVersion, CurrentSchemaVersion)
diff --git a/backend/seed/alert_rules.go b/backend/seed/alert_rules.go
index 2ba8244..d428175 100644
--- a/backend/seed/alert_rules.go
+++ b/backend/seed/alert_rules.go
@@ -82,7 +82,9 @@ var builtinRules = []builtinRule{
{name: "应用错误激增(error > 10万)", query: `{namespace=~".+"} |~ "(?i)\berror\b"`, windowMin: 15, threshold: 100000, cooldown: 240},
{name: "ingress-nginx 5xx 错误", query: `{app=~"ingress-nginx|nginx-ingress-controller"} |~ " (50[0-4]|5[2-9][0-9]) "`, windowMin: 15, threshold: 2000, cooldown: 240},
{name: "ingress-nginx 4xx 激增", query: `{app=~"ingress-nginx|nginx-ingress-controller"} |~ " 4[0-9][0-9] "`, windowMin: 15, threshold: 20000, cooldown: 240},
- {name: "数据库连接失败", query: `{namespace=~".+"} |~ "(?i)(connection refused|connection reset by peer|too many connections)"`, windowMin: 15, threshold: 1000, cooldown: 240},
+ // 数据库连接类错误:覆盖 MySQL / PostgreSQL / Redis 的主流报错文案
+ // (详见函数上方注释,不要加裸 "EOF" / "timeout",那会匹配到几乎所有网络日志)
+ {name: "数据库连接失败", query: `{namespace=~".+"} |~ "(?i)(connection refused|connection reset by peer|broken pipe|no route to host|cannot assign requested address|connection timed out|i/o timeout|too many connections|too many clients already|max number of clients reached|connection pool|connection is not available|could not create connection|communications link failure|server closed the connection unexpectedly|unexpected EOF|driver: bad connection|FATAL:\\s*(password authentication failed|sorry, too many clients|terminating connection)|NOAUTH|WRONGPASS|READONLY you can.t write|CLUSTERDOWN)"`, windowMin: 15, threshold: 1000, cooldown: 240},
}
// seedBuiltinAlertRules 为每个平台管理的 LogSource(managed=true)补齐内置规则:
diff --git a/frontend/src/i18n/locales/en/alerts.ts b/frontend/src/i18n/locales/en/alerts.ts
index 06d21f4..ea04391 100644
--- a/frontend/src/i18n/locales/en/alerts.ts
+++ b/frontend/src/i18n/locales/en/alerts.ts
@@ -90,7 +90,9 @@ export default {
channelDisabled: '(disabled)',
window: 'Window (min)',
threshold: 'Threshold (matches)',
- thresholdHint: '0 = alert on any match',
+ windowHint: 'Look back this many minutes',
+ thresholdHint: 'Alert only above this count',
+ cooldownHint: 'No repeat within cooldown',
cooldown: 'Cooldown (min)',
enabled: 'Enable rule',
},
diff --git a/frontend/src/i18n/locales/zh/alerts.ts b/frontend/src/i18n/locales/zh/alerts.ts
index b3e68e0..576b4bb 100644
--- a/frontend/src/i18n/locales/zh/alerts.ts
+++ b/frontend/src/i18n/locales/zh/alerts.ts
@@ -86,8 +86,10 @@ export default {
channelDisabled: '(禁用)',
window: '统计窗口(分)',
threshold: '触发阈值(条)',
- thresholdHint: '0 = 出现即告警',
+ windowHint: '统计最近多少分钟',
+ thresholdHint: '超过该条数才告警',
cooldown: '冷却(分)',
+ cooldownHint: '冷却内不重复发送',
enabled: '启用规则',
},
}
diff --git a/frontend/src/pages/alerts/index.tsx b/frontend/src/pages/alerts/index.tsx
index 9ca4fe1..bbb3715 100644
--- a/frontend/src/pages/alerts/index.tsx
+++ b/frontend/src/pages/alerts/index.tsx
@@ -442,26 +442,31 @@ const AlertsPage = memo(function AlertsPage() {
{t('form.windowHint')}
{t('form.thresholdHint')}
{t('form.thresholdHint')}
{t('form.cooldownHint')}