发布 0.3.0:恢复 Chat 接口并增强云端事件通知
This commit is contained in:
+121
-24
@@ -57,9 +57,6 @@ type LogEventService struct {
|
||||
|
||||
relayPollTick time.Duration // 订阅确认轮询间隔,零值用默认
|
||||
relayPollTimeout time.Duration // 订阅确认轮询上限,零值用默认
|
||||
|
||||
alertMu sync.Mutex // 保护告警规则冷却表
|
||||
alertSentAt map[uint]time.Time // 规则 ID → 上次告警时刻(阈值型规则冷却)
|
||||
}
|
||||
|
||||
// NewLogEventService 组装依赖;调用 StartParser / StartCleanup 后台协程后生效。
|
||||
@@ -373,20 +370,18 @@ func (s *LogEventService) parseOnce(ctx context.Context) {
|
||||
if len(events) == 0 {
|
||||
return
|
||||
}
|
||||
rules := s.loadEnabledAlertRules(ctx)
|
||||
for i := range events {
|
||||
s.processLogEvent(ctx, &events[i], rules)
|
||||
s.processLogEvent(ctx, &events[i])
|
||||
}
|
||||
}
|
||||
|
||||
// processLogEvent 仅在条件更新命中原行后触发通知,删除并发胜出时静默跳过。
|
||||
func (s *LogEventService) processLogEvent(ctx context.Context, event *model.LogEvent, rules []model.AlertRule) {
|
||||
func (s *LogEventService) processLogEvent(ctx context.Context, event *model.LogEvent) {
|
||||
parsed := parseLogEvent([]byte(event.Payload))
|
||||
if !s.updateParsedEvent(ctx, event, parsed) {
|
||||
return
|
||||
}
|
||||
s.notifyCritical(ctx, event, parsed)
|
||||
s.matchAlertRules(ctx, rules, event, parsed)
|
||||
}
|
||||
|
||||
// updateParsedEvent 用条件 UPDATE 禁止 Save 在删除后隐式重建事件。
|
||||
@@ -413,26 +408,49 @@ func (s *LogEventService) updateParsedEvent(ctx context.Context, event *model.Lo
|
||||
// onsEnvelope 覆盖 ONS 消息与 CloudEvents 审计事件的常见字段;
|
||||
// 真实格式以联调实测为准,提不出字段时只置 Processed 不回填。
|
||||
type onsEnvelope struct {
|
||||
EventType string `json:"eventType"`
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
EventTime string `json:"eventTime"`
|
||||
ResourceName string `json:"resourceName"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
Identity *onsIdentity `json:"identity"`
|
||||
EventType string `json:"eventType"`
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
EventTime string `json:"eventTime"`
|
||||
ResourceName string `json:"resourceName"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
Identity *onsIdentity `json:"identity"`
|
||||
AdditionalDetails *onsAddDetails `json:"additionalDetails"`
|
||||
StateChange *onsStateChange `json:"stateChange"`
|
||||
}
|
||||
|
||||
// onsIdentity 是 Audit 事件 data.identity 中与展示相关的字段。
|
||||
type onsIdentity struct {
|
||||
IPAddress string `json:"ipAddress"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
PrincipalName string `json:"principalName"`
|
||||
}
|
||||
|
||||
// parsedEvent 是从消息原文提取的展示字段集;ResourceName 仅供 P2 推送文案,不落库。
|
||||
// onsAddDetails 是 IDCS 登录类事件 data.additionalDetails 的补充字段;
|
||||
// AuditEventMapValue 为嵌套的 JSON 字符串(含 eventId 成败与失败原因)。
|
||||
type onsAddDetails struct {
|
||||
ActorName string `json:"actorName"`
|
||||
ClientIP string `json:"clientIp"`
|
||||
AuditEventMapValue string `json:"auditEventMapValue"`
|
||||
}
|
||||
|
||||
// onsStateChange 承载 Audit v2 的资源变更快照;description 供策略类事件推送文案。
|
||||
type onsStateChange struct {
|
||||
Current struct {
|
||||
Description string `json:"description"`
|
||||
} `json:"current"`
|
||||
}
|
||||
|
||||
// parsedEvent 是从消息原文提取的展示字段集;ResourceName/Actor/Outcome/Detail
|
||||
// 仅供 P2 推送文案,不落库。
|
||||
type parsedEvent struct {
|
||||
EventType string
|
||||
Source string
|
||||
SourceIP string
|
||||
ResourceName string
|
||||
Actor string // 操作者(identity.principalName,登录事件回退 actorName)
|
||||
Outcome string // 成功 / 失败 / 空(判读不出)
|
||||
Detail string // 补充说明(策略描述、登录失败原因等)
|
||||
EventTime *time.Time
|
||||
}
|
||||
|
||||
@@ -455,7 +473,7 @@ func parseLogEvent(payload []byte) parsedEvent {
|
||||
return envelopeFields(env)
|
||||
}
|
||||
|
||||
// mergeEnvelope 外层缺失字段时以 data 内层补齐(Audit 的 identity 在内层)。
|
||||
// mergeEnvelope 外层缺失字段时以 data 内层补齐(Audit 的 identity 等在内层)。
|
||||
func mergeEnvelope(outer, inner onsEnvelope) onsEnvelope {
|
||||
if outer.EventType == "" {
|
||||
outer.EventType = inner.EventType
|
||||
@@ -472,13 +490,22 @@ func mergeEnvelope(outer, inner onsEnvelope) onsEnvelope {
|
||||
if outer.ResourceName == "" {
|
||||
outer.ResourceName = inner.ResourceName
|
||||
}
|
||||
if outer.Message == "" {
|
||||
outer.Message = inner.Message
|
||||
}
|
||||
if outer.Identity == nil {
|
||||
outer.Identity = inner.Identity
|
||||
}
|
||||
if outer.AdditionalDetails == nil {
|
||||
outer.AdditionalDetails = inner.AdditionalDetails
|
||||
}
|
||||
if outer.StateChange == nil {
|
||||
outer.StateChange = inner.StateChange
|
||||
}
|
||||
return outer
|
||||
}
|
||||
|
||||
// envelopeFields 收敛字段别名并解析事件时间。
|
||||
// envelopeFields 收敛字段别名并解析事件时间与推送用补充字段。
|
||||
func envelopeFields(env onsEnvelope) parsedEvent {
|
||||
eventType := env.EventType
|
||||
if eventType == "" {
|
||||
@@ -490,19 +517,81 @@ func envelopeFields(env onsEnvelope) parsedEvent {
|
||||
eventTime = &ts
|
||||
}
|
||||
}
|
||||
ip := ""
|
||||
if env.Identity != nil {
|
||||
ip = env.Identity.IPAddress
|
||||
}
|
||||
outcome, detail := envOutcome(env)
|
||||
return parsedEvent{
|
||||
EventType: clip(eventType, 128),
|
||||
Source: clip(env.Source, 64),
|
||||
SourceIP: clip(ip, 64),
|
||||
SourceIP: clip(envIP(env), 64),
|
||||
ResourceName: clip(env.ResourceName, 128),
|
||||
Actor: clipRunes(envActor(env), 64),
|
||||
Outcome: outcome,
|
||||
Detail: clipRunes(detail, 200),
|
||||
EventTime: eventTime,
|
||||
}
|
||||
}
|
||||
|
||||
// envIP 取发起方 IP:Audit 的 identity.ipAddress,登录事件回退 additionalDetails.clientIp。
|
||||
func envIP(env onsEnvelope) string {
|
||||
if env.Identity != nil && env.Identity.IPAddress != "" {
|
||||
return env.Identity.IPAddress
|
||||
}
|
||||
if env.AdditionalDetails != nil {
|
||||
return env.AdditionalDetails.ClientIP
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// envActor 取操作者:Audit 的 identity.principalName,登录事件回退 additionalDetails.actorName。
|
||||
func envActor(env onsEnvelope) string {
|
||||
if env.Identity != nil && env.Identity.PrincipalName != "" {
|
||||
return env.Identity.PrincipalName
|
||||
}
|
||||
if env.AdditionalDetails != nil {
|
||||
return env.AdditionalDetails.ActorName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// envOutcome 判读事件成败与补充说明:登录事件解析 auditEventMapValue;其余
|
||||
// Audit 事件看 message 后缀,补充说明取 stateChange.current.description(策略描述)。
|
||||
func envOutcome(env onsEnvelope) (outcome, detail string) {
|
||||
if env.StateChange != nil {
|
||||
detail = env.StateChange.Current.Description
|
||||
}
|
||||
if env.AdditionalDetails != nil && env.AdditionalDetails.AuditEventMapValue != "" {
|
||||
return ssoOutcome(env.AdditionalDetails.AuditEventMapValue, detail)
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(env.Message, " succeeded"):
|
||||
outcome = "成功"
|
||||
case strings.HasSuffix(env.Message, " failed"):
|
||||
outcome = "失败"
|
||||
}
|
||||
return outcome, detail
|
||||
}
|
||||
|
||||
// ssoOutcome 解析 IDCS 审计负载(JSON 字符串):eventId 含 success/failure 定成败,
|
||||
// 失败时以其 message 作为原因说明。
|
||||
func ssoOutcome(raw, detail string) (string, string) {
|
||||
var ev struct {
|
||||
EventID string `json:"eventId"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if json.Unmarshal([]byte(raw), &ev) != nil {
|
||||
return "", detail
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(ev.EventID, "success"):
|
||||
return "成功", detail
|
||||
case strings.Contains(ev.EventID, "failure"):
|
||||
if ev.Message != "" {
|
||||
detail = ev.Message
|
||||
}
|
||||
return "失败", detail
|
||||
}
|
||||
return "", detail
|
||||
}
|
||||
|
||||
// clip 按模型列宽截断解析出的字段。
|
||||
func clip(s string, max int) string {
|
||||
if len(s) > max {
|
||||
@@ -511,6 +600,15 @@ func clip(s string, max int) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// clipRunes 按字符数截断(通知文案用,中文安全)。
|
||||
func clipRunes(s string, max int) string {
|
||||
r := []rune(s)
|
||||
if len(r) > max {
|
||||
return string(r[:max])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// StartCleanup 启动周期清理:启动即清一次,之后每 24h 一次,随 ctx 取消退出。
|
||||
func (s *LogEventService) StartCleanup(ctx context.Context) {
|
||||
s.wg.Add(1)
|
||||
@@ -535,7 +633,6 @@ func (s *LogEventService) cleanupOnce(ctx context.Context) {
|
||||
if err := s.cleanup(ctx, logEventRetention, logEventMaxRows); err != nil {
|
||||
log.Printf("log event cleanup: %v", err)
|
||||
}
|
||||
s.cleanupAlertHits(ctx)
|
||||
}
|
||||
|
||||
// cleanup 先删过期记录,再对超量部分删最旧;阈值参数化便于测试。
|
||||
|
||||
Reference in New Issue
Block a user