fix: make log alert delivery durable
This commit is contained in:
@@ -1,29 +1,31 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AlertOutbox 表示待发送或重试中的告警任务。
|
||||
type AlertOutbox struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// LogEventID 关联日志事件 ID。
|
||||
LogEventID uint `gorm:"index" json:"log_event_id"`
|
||||
// PayloadJSON 保存 AlertReceiveBody 的 JSON 文本。
|
||||
PayloadJSON string `gorm:"type:text" json:"payload_json"`
|
||||
// Status 任务状态:pending/retrying/sent/dead。
|
||||
Status string `gorm:"size:32;index" json:"status"`
|
||||
// RetryCount 已重试次数。
|
||||
RetryCount int `json:"retry_count"`
|
||||
// NextRetryAt 下一次可重试时间。
|
||||
NextRetryAt time.Time `gorm:"index" json:"next_retry_at"`
|
||||
// LastError 最近一次错误信息。
|
||||
LastError string `gorm:"type:text" json:"last_error"`
|
||||
}
|
||||
|
||||
func (AlertOutbox) TableName() string {
|
||||
return "logs_alert_outbox"
|
||||
}
|
||||
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AlertOutbox 表示待发送或重试中的告警任务。
|
||||
type AlertOutbox struct {
|
||||
ID uint `gorm:"primaryKey;index:idx_logs_alert_outbox_event_latest,priority:2" json:"id"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// LogEventID 关联日志事件 ID。
|
||||
LogEventID uint `gorm:"index;index:idx_logs_alert_outbox_event_latest,priority:1" json:"log_event_id"`
|
||||
// PayloadJSON 保存 AlertReceiveBody 的 JSON 文本。
|
||||
PayloadJSON string `gorm:"type:text" json:"payload_json"`
|
||||
// Status 任务状态:pending/processing/retrying/sent/dead。
|
||||
Status string `gorm:"size:32;index" json:"status"`
|
||||
// RetryCount 已重试次数。
|
||||
RetryCount int `json:"retry_count"`
|
||||
// NextRetryAt 下一次可重试时间。
|
||||
NextRetryAt time.Time `gorm:"index" json:"next_retry_at"`
|
||||
// LeaseUntil/LeaseOwner 用于多实例原子领取,避免同一任务并发发送。
|
||||
LeaseUntil *time.Time `gorm:"index" json:"lease_until,omitempty"`
|
||||
LeaseOwner string `gorm:"size:128;index" json:"lease_owner"`
|
||||
// LastError 最近一次错误信息。
|
||||
LastError string `gorm:"type:text" json:"last_error"`
|
||||
}
|
||||
|
||||
func (AlertOutbox) TableName() string {
|
||||
return "logs_alert_outbox"
|
||||
}
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// LogEvent 表示一条归一化/存储后的日志事件。
|
||||
type LogEvent struct {
|
||||
// ID 是数据库主键。
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
// CreatedAt 记录创建时间(写入日志事件时)。
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// SourceKind 表示日志来源类型(例如 trap/syslog 等)。
|
||||
SourceKind string `gorm:"size:16;index" json:"source_kind"`
|
||||
// RemoteAddr 表示日志发送方地址。
|
||||
RemoteAddr string `gorm:"size:64" json:"remote_addr"`
|
||||
// RawPayload 保存原始负载内容。
|
||||
RawPayload string `gorm:"type:text" json:"raw_payload"`
|
||||
// NormalizedSummary 保存归一化后的摘要信息。
|
||||
NormalizedSummary string `gorm:"type:text" json:"normalized_summary"`
|
||||
// NormalizedDetail 保存归一化后的详细信息。
|
||||
NormalizedDetail string `gorm:"type:text" json:"normalized_detail"`
|
||||
// DeviceName 表示关联设备名称。
|
||||
DeviceName string `gorm:"size:512;index" json:"device_name"`
|
||||
// SourceIP 表示原始来源 IP(不含端口)。
|
||||
SourceIP string `gorm:"size:64;index" json:"source_ip"`
|
||||
// ResourceType 表示关联到的资源类型。
|
||||
ResourceType string `gorm:"size:32;index" json:"resource_type"`
|
||||
// ResourceID 表示关联到的资源 ID。
|
||||
ResourceID string `gorm:"size:128;index" json:"resource_id"`
|
||||
// ResourceName 表示关联到的资源名称。
|
||||
ResourceName string `gorm:"size:256" json:"resource_name"`
|
||||
// MatchMethod 表示资源命中方式(ip/hostname/none)。
|
||||
MatchMethod string `gorm:"size:32" json:"match_method"`
|
||||
// DispatchStatus 表示告警分发状态(not_applicable/pending/retrying/sent/dead)。
|
||||
DispatchStatus string `gorm:"size:32;index" json:"dispatch_status"`
|
||||
// SeverityCode 表示告警/严重度编码。
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
// TrapOID 表示关联的 Trap OID(若来源为 trap)。
|
||||
TrapOID string `gorm:"size:512;index" json:"trap_oid"`
|
||||
// AlertSent 表示是否已将告警发送出去。
|
||||
AlertSent bool `json:"alert_sent"`
|
||||
}
|
||||
|
||||
func (LogEvent) TableName() string {
|
||||
return "logs_events"
|
||||
}
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// LogEvent 表示一条归一化/存储后的日志事件。
|
||||
type LogEvent struct {
|
||||
// ID 是数据库主键。
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
// CreatedAt 记录创建时间(写入日志事件时)。
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// SourceKind 表示日志来源类型(例如 trap/syslog 等)。
|
||||
SourceKind string `gorm:"size:16;index" json:"source_kind"`
|
||||
// RemoteAddr 表示日志发送方地址。
|
||||
RemoteAddr string `gorm:"size:64" json:"remote_addr"`
|
||||
// RawPayload 保存原始负载内容。
|
||||
RawPayload string `gorm:"type:text" json:"raw_payload"`
|
||||
// NormalizedSummary 保存归一化后的摘要信息。
|
||||
NormalizedSummary string `gorm:"type:text" json:"normalized_summary"`
|
||||
// NormalizedDetail 保存归一化后的详细信息。
|
||||
NormalizedDetail string `gorm:"type:text" json:"normalized_detail"`
|
||||
// DeviceName 表示关联设备名称。
|
||||
DeviceName string `gorm:"size:512;index" json:"device_name"`
|
||||
// SourceIP 表示原始来源 IP(不含端口)。
|
||||
SourceIP string `gorm:"size:64;index" json:"source_ip"`
|
||||
// ResourceType 表示关联到的资源类型。
|
||||
ResourceType string `gorm:"size:32;index" json:"resource_type"`
|
||||
// ResourceUID 是跨服务使用的规范资源标识。
|
||||
ResourceUID string `gorm:"size:255;index" json:"resource_uid"`
|
||||
// ResourceID 表示关联到的资源 ID。
|
||||
ResourceID string `gorm:"size:128;index" json:"resource_id"`
|
||||
// ResourceName 表示关联到的资源名称。
|
||||
ResourceName string `gorm:"size:256" json:"resource_name"`
|
||||
// MatchMethod 表示资源命中方式(ip/hostname/none)。
|
||||
MatchMethod string `gorm:"size:32" json:"match_method"`
|
||||
// DispatchStatus 表示告警分发状态(not_applicable/pending/retrying/sent/dead)。
|
||||
DispatchStatus string `gorm:"size:32;index" json:"dispatch_status"`
|
||||
// DispatchOutboxID 标识当前一次分发,防止旧任务覆盖较新的重放状态。
|
||||
DispatchOutboxID uint `gorm:"not null;default:0;index" json:"dispatch_outbox_id"`
|
||||
// SeverityCode 表示告警/严重度编码。
|
||||
SeverityCode string `gorm:"size:32" json:"severity_code"`
|
||||
// TrapOID 表示关联的 Trap OID(若来源为 trap)。
|
||||
TrapOID string `gorm:"size:512;index" json:"trap_oid"`
|
||||
// AlertSent 表示是否已将告警发送出去。
|
||||
AlertSent bool `json:"alert_sent"`
|
||||
}
|
||||
|
||||
func (LogEvent) TableName() string {
|
||||
return "logs_events"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ func InitData(db *gorm.DB) error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
if err := backfillDispatchOutboxIDs(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seedDefaultSyslogRules(db); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -35,6 +38,52 @@ func InitData(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func backfillDispatchOutboxIDs(db *gorm.DB) error {
|
||||
const postgresUpdate = `
|
||||
WITH latest AS (
|
||||
SELECT target_event.id AS log_event_id, current_outbox.id, current_outbox.status
|
||||
FROM logs_events AS target_event
|
||||
JOIN LATERAL (
|
||||
SELECT id, status
|
||||
FROM logs_alert_outbox
|
||||
WHERE log_event_id = target_event.id
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
) AS current_outbox ON TRUE
|
||||
WHERE target_event.dispatch_outbox_id = 0
|
||||
)
|
||||
UPDATE logs_events AS target_event
|
||||
SET dispatch_outbox_id = latest.id,
|
||||
dispatch_status = CASE latest.status WHEN 'processing' THEN 'retrying' ELSE latest.status END,
|
||||
alert_sent = CASE WHEN latest.status = 'sent' THEN TRUE ELSE FALSE END
|
||||
FROM latest
|
||||
WHERE target_event.id = latest.log_event_id
|
||||
AND target_event.dispatch_outbox_id = 0`
|
||||
const mysqlUpdate = `
|
||||
UPDATE logs_events AS target_event
|
||||
JOIN (
|
||||
SELECT candidate.id AS log_event_id, MAX(outbox.id) AS id
|
||||
FROM logs_events AS candidate
|
||||
JOIN logs_alert_outbox AS outbox ON outbox.log_event_id = candidate.id
|
||||
WHERE candidate.dispatch_outbox_id = 0
|
||||
GROUP BY candidate.id
|
||||
) AS latest_id ON latest_id.log_event_id = target_event.id
|
||||
JOIN logs_alert_outbox AS latest ON latest.id = latest_id.id
|
||||
SET dispatch_outbox_id = latest.id,
|
||||
dispatch_status = CASE latest.status WHEN 'processing' THEN 'retrying' ELSE latest.status END,
|
||||
alert_sent = CASE WHEN latest.status = 'sent' THEN TRUE ELSE FALSE END
|
||||
WHERE target_event.dispatch_outbox_id = 0`
|
||||
|
||||
switch db.Dialector.Name() {
|
||||
case "postgres":
|
||||
return db.Exec(postgresUpdate).Error
|
||||
case "mysql":
|
||||
return db.Exec(mysqlUpdate).Error
|
||||
default:
|
||||
return gorm.ErrUnsupportedDriver
|
||||
}
|
||||
}
|
||||
|
||||
func seedDefaultSyslogRules(db *gorm.DB) error {
|
||||
var cnt int64
|
||||
if err := db.Model(&SyslogRule{}).Count(&cnt).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user