package models import ( "encoding/json" "fmt" "strings" "github.com/didi/nightingale/v5/vos" "github.com/toolkits/pkg/logger" "github.com/toolkits/pkg/str" "xorm.io/builder" ) type AlertEvent struct { Id int64 `json:"id"` RuleId int64 `json:"rule_id"` RuleName string `json:"rule_name"` RuleNote string `json:"rule_note"` ProcessorUid int64 `json:"processor_uid"` ProcessorObjs User `json:"processor_user_objs" xorm:"-"` EventNote string `json:"event_note"` HashId string `json:"hash_id"` // 唯一标识 IsPromePull int `json:"is_prome_pull"` // 代表是否是prometheus pull告警,为1时前端使用 ReadableExpression 拉取最近1小时数据 LastSend bool `json:"last_sent" xorm:"-"` // true 代表上次发了,false代表还没发:给prometheus做for判断的 AlertDuration int64 `xorm:"-" json:"alert_duration"` // 告警统计周期,PULL模型会当做P8S的for时间 ResClasspaths string `json:"res_classpaths"` ResIdent string `json:"res_ident" xorm:"-"` // res_ident会出现在tags字段,就不用单独写入数据库了,但是各块逻辑中有个单独的res_ident字段更便于处理,所以struct里还留有这个字段;前端不用展示这个字段 Priority int `json:"priority"` Status int `json:"status"` // 标识是否 被屏蔽 IsRecovery int `json:"is_recovery" xorm:"-"` // 0: alert, 1: recovery HistoryPoints json.RawMessage `json:"history_points"` // HistoryPoints{} TriggerTime int64 `json:"trigger_time"` Values string `json:"values" xorm:"-"` // e.g. cpu.idle: 23.3; load.1min: 32 NotifyChannels string `json:"notify_channels"` NotifyGroups string `json:"notify_groups"` NotifyUsers string `json:"notify_users"` RunbookUrl string `json:"runbook_url"` ReadableExpression string `json:"readable_expression"` // e.g. mem.bytes.used.percent(all,60s) > 0 Tags string `json:"tags"` // merge data_tags rule_tags and res_tags NotifyGroupObjs []UserGroup `json:"notify_group_objs" xorm:"-"` NotifyUserObjs []User `json:"notify_user_objs" xorm:"-"` TagMap map[string]string `json:"tag_map" xorm:"-"` } // IsAlert 语法糖,避免直接拿IsRecovery字段做比对不直观易出错 func (ae *AlertEvent) IsAlert() bool { return ae.IsRecovery != 1 } // IsRecov 语法糖,避免直接拿IsRecovery字段做比对不直观易出错 func (ae *AlertEvent) IsRecov() bool { return ae.IsRecovery == 1 } // MarkAlert 语法糖,标记为告警状态 func (ae *AlertEvent) MarkAlert() { ae.IsRecovery = 0 } // MarkRecov 语法糖,标记为恢复状态 func (ae *AlertEvent) MarkRecov() { ae.IsRecovery = 1 } // MarkMuted 语法糖,标记为屏蔽状态 func (ae *AlertEvent) MarkMuted() { ae.Status = 1 } func (ae *AlertEvent) String() string { return fmt.Sprintf("id:%d,rule_id:%d,rule_name:%s,rule_note:%s,hash_id:%s,is_prome_pull:%d,alert_duration:%d,res_classpaths:%s,res_ident:%s,priority:%d,status:%d,is_recovery:%d,history_points:%s,trigger_time:%d,values:%s,notify_channels:%s,runbook_url:%s,readable_expression:%s,tags:%s,notify_group_objs:%+v,notify_user_objs:%+v,tag_map:%v", ae.Id, ae.RuleId, ae.RuleName, ae.RuleNote, ae.HashId, ae.IsPromePull, ae.AlertDuration, ae.ResClasspaths, ae.ResIdent, ae.Priority, ae.Status, ae.IsRecovery, string(ae.HistoryPoints), ae.TriggerTime, ae.Values, ae.NotifyChannels, ae.RunbookUrl, ae.ReadableExpression, ae.Tags, ae.NotifyGroupObjs, ae.NotifyUserObjs, ae.TagMap) } func (ae *AlertEvent) TableName() string { return "alert_event" } func (ae *AlertEvent) FillObjs() error { userGroupIds := strings.Fields(ae.NotifyGroups) if len(userGroupIds) > 0 { groups, err := UserGroupGetsByIdsStr(userGroupIds) if err != nil { return err } ae.NotifyGroupObjs = groups } userIds := strings.Fields(ae.NotifyUsers) if len(userIds) > 0 { users, err := UserGetsByIdsStr(userIds) if err != nil { return err } ae.NotifyUserObjs = users } if ae.ProcessorUid != 0 { processor, err := UserGetById(ae.ProcessorUid) if err != nil { return err } ae.ProcessorObjs = *processor } return nil } func (ae *AlertEvent) GetHistoryPoints() ([]vos.HistoryPoints, error) { historyPoints := []vos.HistoryPoints{} err := json.Unmarshal([]byte(ae.HistoryPoints), &historyPoints) return historyPoints, err } func (ae *AlertEvent) Add() error { return DBInsertOne(ae) } func (ar *AlertEvent) DelByHashId() error { _, err := DB.Where("hash_id=?", ar.HashId).Delete(new(AlertEvent)) if err != nil { logger.Errorf("mysql.error: delete alert_event fail: %v", err) return internalServerError } return nil } func (ar *AlertEvent) HashIdExists() (bool, error) { num, err := DB.Where("hash_id=?", ar.HashId).Count(new(AlertEvent)) return num > 0, err } func (ar *AlertEvent) Del() error { _, err := DB.Where("id=?", ar.Id).Delete(new(AlertEvent)) if err != nil { logger.Errorf("mysql.error: delete alert_event fail: %v", err) return internalServerError } return nil } func AlertEventsDel(ids []int64) error { if len(ids) == 0 { return fmt.Errorf("param ids is empty") } _, err := DB.Exec("DELETE FROM alert_event where id in (" + str.IdsString(ids) + ")") if err != nil { logger.Errorf("mysql.error: delete alert_event(%v) fail: %v", ids, err) return internalServerError } return nil } func AlertEventTotal(stime, etime int64, query string, status, priority int) (num int64, err error) { cond := builder.NewCond() if stime != 0 && etime != 0 { cond = cond.And(builder.Between{Col: "trigger_time", LessVal: stime, MoreVal: etime}) } if status != -1 { cond = cond.And(builder.Eq{"status": status}) } if priority != -1 { cond = cond.And(builder.Eq{"priority": priority}) } if query != "" { arr := strings.Fields(query) for i := 0; i < len(arr); i++ { qarg := "%" + arr[i] + "%" innerCond := builder.NewCond() innerCond = innerCond.Or(builder.Like{"res_classpaths", qarg}) innerCond = innerCond.Or(builder.Like{"rule_name", qarg}) innerCond = innerCond.Or(builder.Like{"tags", qarg}) cond = cond.And(innerCond) } } num, err = DB.Where(cond).Count(new(AlertEvent)) if err != nil { logger.Errorf("mysql.error: count alert_event fail: %v", err) return 0, internalServerError } return num, nil } func AlertEventGets(stime, etime int64, query string, status, priority int, limit, offset int) ([]AlertEvent, error) { cond := builder.NewCond() if stime != 0 && etime != 0 { cond = cond.And(builder.Between{Col: "trigger_time", LessVal: stime, MoreVal: etime}) } if status != -1 { cond = cond.And(builder.Eq{"status": status}) } if priority != -1 { cond = cond.And(builder.Eq{"priority": priority}) } if query != "" { arr := strings.Fields(query) for i := 0; i < len(arr); i++ { qarg := "%" + arr[i] + "%" innerCond := builder.NewCond() innerCond = innerCond.Or(builder.Like{"res_classpaths", qarg}) innerCond = innerCond.Or(builder.Like{"rule_name", qarg}) innerCond = innerCond.Or(builder.Like{"tags", qarg}) cond = cond.And(innerCond) } } var objs []AlertEvent err := DB.Where(cond).Desc("trigger_time").Limit(limit, offset).Find(&objs) if err != nil { logger.Errorf("mysql.error: query alert_event fail: %v", err) return objs, internalServerError } if len(objs) == 0 { return []AlertEvent{}, nil } return objs, nil } func AlertEventGet(where string, args ...interface{}) (*AlertEvent, error) { var obj AlertEvent has, err := DB.Where(where, args...).Get(&obj) if err != nil { logger.Errorf("mysql.error: query alert_event(%s)%+v fail: %s", where, args, err) return nil, internalServerError } if !has { return nil, nil } return &obj, nil } func AlertEventUpdateEventNote(id int64, hashId string, note string, uid int64) error { session := DB.NewSession() defer session.Close() if err := session.Begin(); err != nil { return err } if _, err := session.Exec("UPDATE alert_event SET event_note = ?, processor_uid = ? WHERE id = ?", note, uid, id); err != nil { logger.Errorf("mysql.error: update alert_event event_note fail: %v", err) return err } if _, err := session.Exec("UPDATE history_alert_event SET event_note = ?, processor_uid = ? WHERE hash_id = ?", note, uid, hashId); err != nil { logger.Errorf("mysql.error: update history_alert_event event_note fail: %v", err) return err } return session.Commit() }