Compare commits

..

7 Commits

Author SHA1 Message Date
ning
b057940134 add stats 2024-08-01 15:18:49 +08:00
ning
4500c4aba8 refactor callback 2024-08-01 15:12:03 +08:00
710leo
726e994d58 merge main 2024-07-30 15:58:31 +08:00
710leo
71c4c24f00 code refactor 2024-07-30 15:57:15 +08:00
710leo
d14a834149 webhook send batch 2024-07-30 15:47:25 +08:00
ning
5f895552a9 done 2024-07-26 12:54:28 +08:00
ning
1e3f62b92f refactor 2024-07-25 17:46:19 +08:00
45 changed files with 124 additions and 1012 deletions

View File

@@ -112,5 +112,5 @@ func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, al
go consumer.LoopConsume()
go queue.ReportQueueSize(alertStats)
go sender.InitEmailSender(ctx, notifyConfigCache)
go sender.InitEmailSender(notifyConfigCache)
}

View File

@@ -167,7 +167,7 @@ func (e *Dispatch) HandleEventNotify(event *models.AlertCurEvent, isSubscribe bo
}
// 处理事件发送,这里用一个goroutine处理一个event的所有发送事件
go e.Send(rule, event, notifyTarget, isSubscribe)
go e.Send(rule, event, notifyTarget)
// 如果是不是订阅规则出现的event, 则需要处理订阅规则的event
if !isSubscribe {
@@ -238,12 +238,11 @@ func (e *Dispatch) handleSub(sub *models.AlertSubscribe, event models.AlertCurEv
e.HandleEventNotify(&event, true)
}
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget, isSubscribe bool) {
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget) {
needSend := e.BeforeSenderHook(event)
if needSend {
for channel, uids := range notifyTarget.ToChannelUserMap() {
msgCtx := sender.BuildMessageContext(e.ctx, rule, []*models.AlertCurEvent{event},
uids, e.userCache, e.Astats)
msgCtx := sender.BuildMessageContext(rule, []*models.AlertCurEvent{event}, uids, e.userCache, e.Astats)
e.RwLock.RLock()
s := e.Senders[channel]
e.RwLock.RUnlock()
@@ -267,18 +266,13 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
// handle global webhooks
if e.alerting.WebhookBatchSend {
sender.BatchSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
sender.BatchSendWebhooks(notifyTarget.ToWebhookList(), event, e.Astats)
} else {
sender.SingleSendWebhooks(e.ctx, notifyTarget.ToWebhookList(), event, e.Astats)
sender.SingleSendWebhooks(notifyTarget.ToWebhookList(), event, e.Astats)
}
if !isSubscribe {
// handle ibex callbacks
e.HandleIbex(rule, event)
// handle plugin call
go sender.MayPluginNotify(e.ctx, e.genNoticeBytes(event), e.notifyConfigCache.
GetNotifyScript(), e.Astats, event)
}
// handle plugin call
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript(), e.Astats)
}
func (e *Dispatch) SendCallbacks(rule *models.AlertRule, notifyTarget *NotifyTarget, event *models.AlertCurEvent) {
@@ -328,30 +322,6 @@ func (e *Dispatch) SendCallbacks(rule *models.AlertRule, notifyTarget *NotifyTar
}
}
func (e *Dispatch) HandleIbex(rule *models.AlertRule, event *models.AlertCurEvent) {
// 解析 RuleConfig 字段
var ruleConfig struct {
TaskTpls []*models.Tpl `json:"task_tpls"`
}
json.Unmarshal([]byte(rule.RuleConfig), &ruleConfig)
for _, t := range ruleConfig.TaskTpls {
if t.TplId == 0 {
continue
}
if len(t.Host) == 0 {
sender.CallIbex(e.ctx, t.TplId, event.TargetIdent,
e.taskTplsCache, e.targetCache, e.userCache, event)
continue
}
for _, host := range t.Host {
sender.CallIbex(e.ctx, t.TplId, host,
e.taskTplsCache, e.targetCache, e.userCache, event)
}
}
}
type Notice struct {
Event *models.AlertCurEvent `json:"event"`
Tpls map[string]string `json:"tpls"`

View File

@@ -53,11 +53,10 @@ type Processor struct {
datasourceId int64
EngineName string
rule *models.AlertRule
fires *AlertCurEventMap
pendings *AlertCurEventMap
pendingsUseByRecover *AlertCurEventMap
inhibit bool
rule *models.AlertRule
fires *AlertCurEventMap
pendings *AlertCurEventMap
inhibit bool
tagsMap map[string]string
tagsArr []string
@@ -233,17 +232,17 @@ func Relabel(rule *models.AlertRule, event *models.AlertCurEvent) {
return
}
if len(rule.EventRelabelConfig) == 0 {
return
}
// need to keep the original label
event.OriginalTags = event.Tags
event.OriginalTagsJSON = make([]string, len(event.TagsJSON))
labels := make([]prompb.Label, len(event.TagsJSON))
for i, tag := range event.TagsJSON {
label := strings.SplitN(tag, "=", 2)
label := strings.Split(tag, "=")
if len(label) != 2 {
logger.Errorf("event%+v relabel: the label length is not 2:%v", event, label)
continue
}
event.OriginalTagsJSON[i] = tag
labels[i] = prompb.Label{Name: label[0], Value: label[1]}
}
@@ -343,22 +342,11 @@ func (p *Processor) RecoverSingle(hash string, now int64, value *string, values
if !has {
return
}
// 如果配置了留观时长,就不能立马恢复了
if cachedRule.RecoverDuration > 0 {
lastPendingEvent, has := p.pendingsUseByRecover.Get(hash)
if !has {
// 说明没有产生过异常点,就不需要恢复了
logger.Debugf("rule_eval:%s event:%v do not has pending event, not recover", p.Key(), event)
return
}
if now-lastPendingEvent.LastEvalTime < cachedRule.RecoverDuration {
logger.Debugf("rule_eval:%s event:%v not recover", p.Key(), event)
return
}
if cachedRule.RecoverDuration > 0 && now-event.LastEvalTime < cachedRule.RecoverDuration {
logger.Debugf("rule_eval:%s event:%v not recover", p.Key(), event)
return
}
if value != nil {
event.TriggerValue = *value
if len(values) > 0 {
@@ -370,7 +358,6 @@ func (p *Processor) RecoverSingle(hash string, now int64, value *string, values
// 我确实无法分辨是prom中有值但是未满足阈值所以没返回还是prom中确实丢了一些点导致没有数据可以返回尴尬
p.fires.Delete(hash)
p.pendings.Delete(hash)
p.pendingsUseByRecover.Delete(hash)
// 可能是因为调整了promql才恢复的所以事件里边要体现最新的promql否则用户会比较困惑
// 当然其实rule的各个字段都可能发生变化了都更新一下吧
@@ -402,11 +389,9 @@ func (p *Processor) handleEvent(events []*models.AlertCurEvent) {
preEvent, has := p.pendings.Get(event.Hash)
if has {
p.pendings.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
p.pendingsUseByRecover.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
preTriggerTime = preEvent.TriggerTime
} else {
p.pendings.Set(event.Hash, event)
p.pendingsUseByRecover.Set(event.Hash, event)
preTriggerTime = event.TriggerTime
}
@@ -492,7 +477,6 @@ func (p *Processor) pushEventToQueue(e *models.AlertCurEvent) {
func (p *Processor) RecoverAlertCurEventFromDb() {
p.pendings = NewAlertCurEventMap(nil)
p.pendingsUseByRecover = NewAlertCurEventMap(nil)
curEvents, err := models.AlertCurEventGetByRuleIdAndDsId(p.ctx, p.rule.Id, p.datasourceId)
if err != nil {
@@ -592,7 +576,6 @@ func (p *Processor) mayHandleGroup() {
func (p *Processor) DeleteProcessEvent(hash string) {
p.fires.Delete(hash)
p.pendings.Delete(hash)
p.pendingsUseByRecover.Delete(hash)
}
func labelMapToArr(m map[string]string) []string {

View File

@@ -125,7 +125,7 @@ func (c *DefaultCallBacker) CallBack(ctx CallBackContext) {
Batch: 1000,
}
PushCallbackEvent(ctx.Ctx, webhookConf, event, ctx.Stats)
PushCallbackEvent(webhookConf, event, ctx.Stats)
return
}
@@ -141,46 +141,16 @@ func (c *DefaultCallBacker) CallBack(ctx CallBackContext) {
}
}
func doSendAndRecord(ctx *ctx.Context, url, token string, body interface{}, channel string,
stats *astats.Stats, event *models.AlertCurEvent) {
res, err := doSend(url, body, channel, stats)
NotifyRecord(ctx, event, channel, token, res, err)
}
func NotifyRecord(ctx *ctx.Context, evt *models.AlertCurEvent, channel, target, res string, err error) {
noti := models.NewNotificationRecord(evt, channel, target)
if err != nil {
noti.SetStatus(models.NotiStatusFailure)
noti.SetDetails(err.Error())
} else if res != "" {
noti.SetDetails(string(res))
}
if !ctx.IsCenter {
_, err := poster.PostByUrlsWithResp[int64](ctx, "/v1/n9e/notify-record", noti)
if err != nil {
logger.Errorf("add noti:%v failed, err: %v", noti, err)
}
return
}
if err := noti.Add(ctx); err != nil {
logger.Errorf("add noti:%v failed, err: %v", noti, err)
}
}
func doSend(url string, body interface{}, channel string, stats *astats.Stats) (string, error) {
func doSend(url string, body interface{}, channel string, stats *astats.Stats) {
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
if err != nil {
logger.Errorf("%s_sender: result=fail url=%s code=%d error=%v req:%v response=%s", channel, url, code, err, body, string(res))
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
return "", err
} else {
logger.Infof("%s_sender: result=succ url=%s code=%d req:%v response=%s", channel, url, code, body, string(res))
}
logger.Infof("%s_sender: result=succ url=%s code=%d req:%v response=%s", channel, url, code, body, string(res))
return string(res), nil
}
type TaskCreateReply struct {
@@ -188,7 +158,7 @@ type TaskCreateReply struct {
Dat int64 `json:"dat"` // task.id
}
func PushCallbackEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
func PushCallbackEvent(webhook *models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
CallbackEventQueueLock.RLock()
queue := CallbackEventQueue[webhook.Url]
CallbackEventQueueLock.RUnlock()
@@ -203,7 +173,7 @@ func PushCallbackEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.
CallbackEventQueue[webhook.Url] = queue
CallbackEventQueueLock.Unlock()
StartConsumer(ctx, queue, webhook.Batch, webhook, stats)
StartConsumer(queue, webhook.Batch, webhook, stats)
}
succ := queue.list.PushFront(event)

View File

@@ -1,10 +1,9 @@
package sender
import (
"github.com/ccfos/nightingale/v6/models"
"html/template"
"strings"
"github.com/ccfos/nightingale/v6/models"
)
type dingtalkMarkdown struct {
@@ -36,13 +35,13 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
return
}
urls, ats, tokens := ds.extract(ctx.Users)
urls, ats := ds.extract(ctx.Users)
if len(urls) == 0 {
return
}
message := BuildTplMessage(models.Dingtalk, ds.tpl, ctx.Events)
for i, url := range urls {
for _, url := range urls {
var body dingtalk
// NoAt in url
if strings.Contains(url, "noat=1") {
@@ -67,7 +66,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
}
}
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Dingtalk, ctx.Stats, ctx.Events[0])
doSend(url, body, models.Dingtalk, ctx.Stats)
}
}
@@ -97,17 +96,15 @@ func (ds *DingtalkSender) CallBack(ctx CallBackContext) {
body.Markdown.Text = message
}
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body,
"callback", ctx.Stats, ctx.Events[0])
doSend(ctx.CallBackURL, body, models.Dingtalk, ctx.Stats)
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}
// extract urls and ats from Users
func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string, []string) {
func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
ats := make([]string, 0, len(users))
tokens := make([]string, 0, len(users))
for _, user := range users {
if user.Phone != "" {
@@ -119,8 +116,7 @@ func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string, []s
url = "https://oapi.dingtalk.com/robot/send?access_token=" + token
}
urls = append(urls, url)
tokens = append(tokens, token)
}
}
return urls, ats, tokens
return urls, ats
}

View File

@@ -9,14 +9,13 @@ import (
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
"gopkg.in/gomail.v2"
)
var mailch chan *EmailContext
var mailch chan *gomail.Message
type EmailSender struct {
subjectTpl *template.Template
@@ -24,11 +23,6 @@ type EmailSender struct {
smtp aconf.SMTPConfig
}
type EmailContext struct {
event *models.AlertCurEvent
mail *gomail.Message
}
func (es *EmailSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
@@ -42,7 +36,7 @@ func (es *EmailSender) Send(ctx MessageContext) {
subject = ctx.Events[0].RuleName
}
content := BuildTplMessage(models.Email, es.contentTpl, ctx.Events)
es.WriteEmail(subject, content, tos, ctx.Events[0])
es.WriteEmail(subject, content, tos)
ctx.Stats.AlertNotifyTotal.WithLabelValues(models.Email).Add(float64(len(tos)))
}
@@ -79,8 +73,7 @@ func SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) err
return nil
}
func (es *EmailSender) WriteEmail(subject, content string, tos []string,
event *models.AlertCurEvent) {
func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
m := gomail.NewMessage()
m.SetHeader("From", es.smtp.From)
@@ -88,7 +81,7 @@ func (es *EmailSender) WriteEmail(subject, content string, tos []string,
m.SetHeader("Subject", subject)
m.SetBody("text/html", content)
mailch <- &EmailContext{event, m}
mailch <- m
}
func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
@@ -111,22 +104,22 @@ func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
var mailQuit = make(chan struct{})
func RestartEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
func RestartEmailSender(smtp aconf.SMTPConfig) {
// Notify internal start exit
mailQuit <- struct{}{}
startEmailSender(ctx, smtp)
startEmailSender(smtp)
}
var smtpConfig aconf.SMTPConfig
func InitEmailSender(ctx *ctx.Context, ncc *memsto.NotifyConfigCacheType) {
mailch = make(chan *EmailContext, 100000)
go updateSmtp(ctx, ncc)
func InitEmailSender(ncc *memsto.NotifyConfigCacheType) {
mailch = make(chan *gomail.Message, 100000)
go updateSmtp(ncc)
smtpConfig = ncc.GetSMTP()
startEmailSender(ctx, smtpConfig)
startEmailSender(smtpConfig)
}
func updateSmtp(ctx *ctx.Context, ncc *memsto.NotifyConfigCacheType) {
func updateSmtp(ncc *memsto.NotifyConfigCacheType) {
for {
time.Sleep(1 * time.Minute)
smtp := ncc.GetSMTP()
@@ -134,12 +127,12 @@ func updateSmtp(ctx *ctx.Context, ncc *memsto.NotifyConfigCacheType) {
smtpConfig.Pass != smtp.Pass || smtpConfig.User != smtp.User || smtpConfig.Port != smtp.Port ||
smtpConfig.InsecureSkipVerify != smtp.InsecureSkipVerify { //diff
smtpConfig = smtp
RestartEmailSender(ctx, smtp)
RestartEmailSender(smtp)
}
}
}
func startEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
func startEmailSender(smtp aconf.SMTPConfig) {
conf := smtp
if conf.Host == "" || conf.Port == 0 {
logger.Warning("SMTP configurations invalid")
@@ -174,8 +167,7 @@ func startEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
}
open = true
}
var err error
if err = gomail.Send(s, m.mail); err != nil {
if err := gomail.Send(s, m); err != nil {
logger.Errorf("email_sender: failed to send: %s", err)
// close and retry
@@ -192,16 +184,11 @@ func startEmailSender(ctx *ctx.Context, smtp aconf.SMTPConfig) {
}
open = true
if err = gomail.Send(s, m.mail); err != nil {
if err := gomail.Send(s, m); err != nil {
logger.Errorf("email_sender: failed to retry send: %s", err)
}
} else {
logger.Infof("email_sender: result=succ subject=%v to=%v",
m.mail.GetHeader("Subject"), m.mail.GetHeader("To"))
}
for _, to := range m.mail.GetHeader("To") {
NotifyRecord(ctx, m.event, models.Email, to, "", err)
logger.Infof("email_sender: result=succ subject=%v to=%v", m.GetHeader("Subject"), m.GetHeader("To"))
}
size++

View File

@@ -54,8 +54,7 @@ func (fs *FeishuSender) CallBack(ctx CallBackContext) {
},
}
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
ctx.Stats, ctx.Events[0])
doSend(ctx.CallBackURL, body, models.Feishu, ctx.Stats)
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}
@@ -63,9 +62,9 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, ats, tokens := fs.extract(ctx.Users)
urls, ats := fs.extract(ctx.Users)
message := BuildTplMessage(models.Feishu, fs.tpl, ctx.Events)
for i, url := range urls {
for _, url := range urls {
body := feishu{
Msgtype: "text",
Content: feishuContent{
@@ -78,14 +77,13 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
IsAtAll: false,
}
}
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Feishu, ctx.Stats, ctx.Events[0])
doSend(url, body, models.Feishu, ctx.Stats)
}
}
func (fs *FeishuSender) extract(users []*models.User) ([]string, []string, []string) {
func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
ats := make([]string, 0, len(users))
tokens := make([]string, 0, len(users))
for _, user := range users {
if user.Phone != "" {
@@ -97,8 +95,7 @@ func (fs *FeishuSender) extract(users []*models.User) ([]string, []string, []str
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + token
}
urls = append(urls, url)
tokens = append(tokens, token)
}
}
return urls, ats, tokens
return urls, ats
}

View File

@@ -134,15 +134,14 @@ func (fs *FeishuCardSender) CallBack(ctx CallBackContext) {
}
parsedURL.RawQuery = ""
doSendAndRecord(ctx.Ctx, parsedURL.String(), parsedURL.String(), body, "callback",
ctx.Stats, ctx.Events[0])
doSend(parsedURL.String(), body, models.FeishuCard, ctx.Stats)
}
func (fs *FeishuCardSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, tokens := fs.extract(ctx.Users)
urls, _ := fs.extract(ctx.Users)
message := BuildTplMessage(models.FeishuCard, fs.tpl, ctx.Events)
color := "red"
lowerUnicode := strings.ToLower(message)
@@ -157,15 +156,14 @@ func (fs *FeishuCardSender) Send(ctx MessageContext) {
body.Card.Header.Template = color
body.Card.Elements[0].Text.Content = message
body.Card.Elements[2].Elements[0].Content = SendTitle
for i, url := range urls {
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.FeishuCard,
ctx.Stats, ctx.Events[0])
for _, url := range urls {
doSend(url, body, models.FeishuCard, ctx.Stats)
}
}
func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
tokens := make([]string, 0, len(users))
ats := make([]string, 0)
for i := range users {
if token, has := users[i].ExtractToken(models.FeishuCard); has {
url := token
@@ -173,8 +171,7 @@ func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
}
urls = append(urls, url)
tokens = append(tokens, token)
}
}
return urls, tokens
return urls, ats
}

View File

@@ -77,20 +77,15 @@ func (c *IbexCallBacker) handleIbex(ctx *ctx.Context, url string, event *models.
return
}
CallIbex(ctx, id, host, c.taskTplCache, c.targetCache, c.userCache, event)
}
func CallIbex(ctx *ctx.Context, id int64, host string,
taskTplCache *memsto.TaskTplCache, targetCache *memsto.TargetCacheType,
userCache *memsto.UserCacheType, event *models.AlertCurEvent) {
tpl := taskTplCache.Get(id)
tpl := c.taskTplCache.Get(id)
if tpl == nil {
logger.Errorf("event_callback_ibex: no such tpl(%d)", id)
return
}
// check perm
// tpl.GroupId - host - account 三元组校验权限
can, err := canDoIbex(tpl.UpdateBy, tpl, host, targetCache, userCache)
can, err := canDoIbex(tpl.UpdateBy, tpl, host, c.targetCache, c.userCache)
if err != nil {
logger.Errorf("event_callback_ibex: check perm fail: %v", err)
return

View File

@@ -27,8 +27,7 @@ func (lk *LarkSender) CallBack(ctx CallBackContext) {
},
}
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
ctx.Stats, ctx.Events[0])
doSend(ctx.CallBackURL, body, models.Lark, ctx.Stats)
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}

View File

@@ -55,8 +55,7 @@ func (fs *LarkCardSender) CallBack(ctx CallBackContext) {
}
parsedURL.RawQuery = ""
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
ctx.Stats, ctx.Events[0])
doSend(parsedURL.String(), body, models.LarkCard, ctx.Stats)
}
func (fs *LarkCardSender) Send(ctx MessageContext) {

View File

@@ -7,7 +7,6 @@ import (
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
)
@@ -39,11 +38,11 @@ func (ms *MmSender) Send(ctx MessageContext) {
}
message := BuildTplMessage(models.Mm, ms.tpl, ctx.Events)
SendMM(ctx.Ctx, MatterMostMessage{
SendMM(MatterMostMessage{
Text: message,
Tokens: urls,
Stats: ctx.Stats,
}, ctx.Events[0])
})
}
func (ms *MmSender) CallBack(ctx CallBackContext) {
@@ -52,11 +51,11 @@ func (ms *MmSender) CallBack(ctx CallBackContext) {
}
message := BuildTplMessage(models.Mm, ms.tpl, ctx.Events)
SendMM(ctx.Ctx, MatterMostMessage{
SendMM(MatterMostMessage{
Text: message,
Tokens: []string{ctx.CallBackURL},
Stats: ctx.Stats,
}, ctx.Events[0])
})
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}
@@ -71,7 +70,7 @@ func (ms *MmSender) extract(users []*models.User) []string {
return tokens
}
func SendMM(ctx *ctx.Context, message MatterMostMessage, event *models.AlertCurEvent) {
func SendMM(message MatterMostMessage) {
for i := 0; i < len(message.Tokens); i++ {
u, err := url.Parse(message.Tokens[i])
if err != nil {
@@ -104,7 +103,7 @@ func SendMM(ctx *ctx.Context, message MatterMostMessage, event *models.AlertCurE
Username: username,
Text: txt + message.Text,
}
doSendAndRecord(ctx, ur, message.Tokens[i], body, models.Mm, message.Stats, event)
doSend(ur, body, models.Mm, message.Stats)
}
}
}

View File

@@ -2,30 +2,26 @@ package sender
import (
"bytes"
"fmt"
"os"
"os/exec"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/sys"
)
func MayPluginNotify(ctx *ctx.Context, noticeBytes []byte, notifyScript models.NotifyScript,
stats *astats.Stats, event *models.AlertCurEvent) {
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
if len(noticeBytes) == 0 {
return
}
alertingCallScript(ctx, noticeBytes, notifyScript, stats, event)
alertingCallScript(noticeBytes, notifyScript, stats)
}
func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models.NotifyScript,
stats *astats.Stats, event *models.AlertCurEvent) {
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
// not enable or no notify.py? do nothing
config := notifyScript
if !config.Enable || config.Content == "" {
@@ -85,7 +81,6 @@ func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models
}
err, isTimeout := sys.WrapTimeout(cmd, time.Duration(config.Timeout)*time.Second)
NotifyRecord(ctx, event, channel, cmd.String(), "", buildErr(err, isTimeout))
if isTimeout {
if err == nil {
@@ -107,11 +102,3 @@ func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models
logger.Infof("event_script_notify_ok: exec %s output: %s", fpath, buf.String())
}
func buildErr(err error, isTimeout bool) error {
if err == nil && !isTimeout {
return nil
} else {
return fmt.Errorf("is_timeout: %v, err: %v", isTimeout, err)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
)
type (
@@ -23,7 +22,6 @@ type (
Rule *models.AlertRule
Events []*models.AlertCurEvent
Stats *astats.Stats
Ctx *ctx.Context
}
)
@@ -51,15 +49,13 @@ func NewSender(key string, tpls map[string]*template.Template, smtp ...aconf.SMT
return nil
}
func BuildMessageContext(ctx *ctx.Context, rule *models.AlertRule, events []*models.AlertCurEvent,
uids []int64, userCache *memsto.UserCacheType, stats *astats.Stats) MessageContext {
func BuildMessageContext(rule *models.AlertRule, events []*models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType, stats *astats.Stats) MessageContext {
users := userCache.GetByUserIds(uids)
return MessageContext{
Rule: rule,
Events: events,
Users: users,
Stats: stats,
Ctx: ctx,
}
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
)
@@ -36,11 +35,11 @@ func (ts *TelegramSender) CallBack(ctx CallBackContext) {
}
message := BuildTplMessage(models.Telegram, ts.tpl, ctx.Events)
SendTelegram(ctx.Ctx, TelegramMessage{
SendTelegram(TelegramMessage{
Text: message,
Tokens: []string{ctx.CallBackURL},
Stats: ctx.Stats,
}, ctx.Events[0])
})
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}
@@ -52,11 +51,11 @@ func (ts *TelegramSender) Send(ctx MessageContext) {
tokens := ts.extract(ctx.Users)
message := BuildTplMessage(models.Telegram, ts.tpl, ctx.Events)
SendTelegram(ctx.Ctx, TelegramMessage{
SendTelegram(TelegramMessage{
Text: message,
Tokens: tokens,
Stats: ctx.Stats,
}, ctx.Events[0])
})
}
func (ts *TelegramSender) extract(users []*models.User) []string {
@@ -69,7 +68,7 @@ func (ts *TelegramSender) extract(users []*models.User) []string {
return tokens
}
func SendTelegram(ctx *ctx.Context, message TelegramMessage, event *models.AlertCurEvent) {
func SendTelegram(message TelegramMessage) {
for i := 0; i < len(message.Tokens); i++ {
if !strings.Contains(message.Tokens[i], "/") && !strings.HasPrefix(message.Tokens[i], "https://") {
logger.Errorf("telegram_sender: result=fail invalid token=%s", message.Tokens[i])
@@ -93,6 +92,6 @@ func SendTelegram(ctx *ctx.Context, message TelegramMessage, event *models.Alert
Text: message.Text,
}
doSendAndRecord(ctx, url, message.Tokens[i], body, models.Telegram, message.Stats, event)
doSend(url, body, models.Telegram, message.Stats)
}
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
@@ -12,12 +11,11 @@ import (
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
)
func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats) (bool, string, error) {
func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats) bool {
channel := "webhook"
if webhook.Type == models.RuleCallback {
channel = "callback"
@@ -25,12 +23,12 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
conf := webhook
if conf.Url == "" || !conf.Enable {
return false, "", nil
return false
}
bs, err := json.Marshal(event)
if err != nil {
logger.Errorf("%s alertingWebhook failed to marshal event:%+v err:%v", channel, event, err)
return false, "", err
return false
}
bf := bytes.NewBuffer(bs)
@@ -38,7 +36,7 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
req, err := http.NewRequest("POST", conf.Url, bf)
if err != nil {
logger.Warningf("%s alertingWebhook failed to new reques event:%s err:%v", channel, string(bs), err)
return true, "", err
return true
}
req.Header.Set("Content-Type", "application/json")
@@ -68,15 +66,14 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
var resp *http.Response
var body []byte
resp, err = client.Do(req)
if err != nil {
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
logger.Errorf("event_%s_fail, event:%s, url: [%s], error: [%s]", channel, string(bs), conf.Url, err)
return true, "", err
return true
}
var body []byte
if resp.Body != nil {
defer resp.Body.Close()
body, _ = io.ReadAll(resp.Body)
@@ -84,19 +81,18 @@ func sendWebhook(webhook *models.Webhook, event interface{}, stats *astats.Stats
if resp.StatusCode == 429 {
logger.Errorf("event_%s_fail, url: %s, response code: %d, body: %s event:%s", channel, conf.Url, resp.StatusCode, string(body), string(bs))
return true, string(body), fmt.Errorf("status code is 429")
return true
}
logger.Debugf("event_%s_succ, url: %s, response code: %d, body: %s event:%s", channel, conf.Url, resp.StatusCode, string(body), string(bs))
return false, string(body), nil
return false
}
func SingleSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
func SingleSendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
for _, conf := range webhooks {
retryCount := 0
for retryCount < 3 {
needRetry, res, err := sendWebhook(conf, event, stats)
NotifyRecord(ctx, event, "webhook", conf.Url, res, err)
needRetry := sendWebhook(conf, event, stats)
if !needRetry {
break
}
@@ -106,10 +102,10 @@ func SingleSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *mod
}
}
func BatchSendWebhooks(ctx *ctx.Context, webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
func BatchSendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
for _, conf := range webhooks {
logger.Infof("push event:%+v to queue:%v", event, conf)
PushEvent(ctx, conf, event, stats)
PushEvent(conf, event, stats)
}
}
@@ -125,7 +121,7 @@ type WebhookQueue struct {
closeCh chan struct{}
}
func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
func PushEvent(webhook *models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
EventQueueLock.RLock()
queue := EventQueue[webhook.Url]
EventQueueLock.RUnlock()
@@ -140,7 +136,7 @@ func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCur
EventQueue[webhook.Url] = queue
EventQueueLock.Unlock()
StartConsumer(ctx, queue, webhook.Batch, webhook, stats)
StartConsumer(queue, webhook.Batch, webhook, stats)
}
succ := queue.list.PushFront(event)
@@ -150,7 +146,7 @@ func PushEvent(ctx *ctx.Context, webhook *models.Webhook, event *models.AlertCur
}
}
func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *models.Webhook, stats *astats.Stats) {
func StartConsumer(queue *WebhookQueue, popSize int, webhook *models.Webhook, stats *astats.Stats) {
for {
select {
case <-queue.closeCh:
@@ -165,21 +161,13 @@ func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *
retryCount := 0
for retryCount < webhook.RetryCount {
needRetry, res, err := sendWebhook(webhook, events, stats)
needRetry := sendWebhook(webhook, events, stats)
if !needRetry {
break
}
go RecordEvents(ctx, webhook, events, stats, res, err)
retryCount++
time.Sleep(time.Second * time.Duration(webhook.RetryInterval) * time.Duration(retryCount))
}
}
}
}
func RecordEvents(ctx *ctx.Context, webhook *models.Webhook, events []*models.AlertCurEvent, stats *astats.Stats, res string, err error) {
for _, event := range events {
time.Sleep(time.Millisecond * 10)
NotifyRecord(ctx, event, "webhook", webhook.Url, res, err)
}
}

View File

@@ -37,8 +37,7 @@ func (ws *WecomSender) CallBack(ctx CallBackContext) {
},
}
doSendAndRecord(ctx.Ctx, ctx.CallBackURL, ctx.CallBackURL, body, "callback",
ctx.Stats, ctx.Events[0])
doSend(ctx.CallBackURL, body, models.Wecom, ctx.Stats)
ctx.Stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
}
@@ -46,22 +45,21 @@ func (ws *WecomSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, tokens := ws.extract(ctx.Users)
urls := ws.extract(ctx.Users)
message := BuildTplMessage(models.Wecom, ws.tpl, ctx.Events)
for i, url := range urls {
for _, url := range urls {
body := wecom{
Msgtype: "markdown",
Markdown: wecomMarkdown{
Content: message,
},
}
doSendAndRecord(ctx.Ctx, url, tokens[i], body, models.Wecom, ctx.Stats, ctx.Events[0])
doSend(url, body, models.Wecom, ctx.Stats)
}
}
func (ws *WecomSender) extract(users []*models.User) ([]string, []string) {
func (ws *WecomSender) extract(users []*models.User) []string {
urls := make([]string, 0, len(users))
tokens := make([]string, 0, len(users))
for _, user := range users {
if token, has := user.ExtractToken(models.Wecom); has {
url := token
@@ -69,8 +67,7 @@ func (ws *WecomSender) extract(users []*models.User) ([]string, []string) {
url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + token
}
urls = append(urls, url)
tokens = append(tokens, token)
}
}
return urls, tokens
return urls
}

View File

@@ -13,7 +13,6 @@ type Center struct {
UseFileAssets bool
FlashDuty FlashDuty
EventHistoryGroupView bool
CleanNotifyRecordDay int
}
type Plugin struct {

View File

@@ -16,7 +16,6 @@ import (
centerrt "github.com/ccfos/nightingale/v6/center/router"
"github.com/ccfos/nightingale/v6/center/sso"
"github.com/ccfos/nightingale/v6/conf"
"github.com/ccfos/nightingale/v6/cron"
"github.com/ccfos/nightingale/v6/dumper"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
@@ -107,8 +106,6 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
go version.GetGithubVersion()
go cron.CleanNotifyRecord(ctx, config.Center.CleanNotifyRecordDay)
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
centerRouter := centerrt.New(config.HTTP, config.Center, config.Alert, config.Ibex, cconf.Operations, dsCache, notifyConfigCache, promClients, tdengineClients,
redis, sso, ctx, metas, idents, targetCache, userCache, userGroupCache)

View File

@@ -315,8 +315,6 @@ func (rt *Router) Config(r *gin.Engine) {
pages.GET("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGets)
pages.POST("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByFE)
pages.POST("/busi-group/:id/alert-rules/import", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByImport)
pages.POST("/busi-group/:id/alert-rules/import-prom-rule", rt.auth(),
rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByImportPromRule)
pages.DELETE("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules/del"), rt.bgrw(), rt.alertRuleDel)
pages.PUT("/busi-group/:id/alert-rules/fields", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.bgrw(), rt.alertRulePutFields)
pages.PUT("/busi-group/:id/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRulePutByFE)
@@ -352,11 +350,9 @@ func (rt *Router) Config(r *gin.Engine) {
if rt.Center.AnonymousAccess.AlertDetail {
pages.GET("/alert-cur-event/:eid", rt.alertCurEventGet)
pages.GET("/alert-his-event/:eid", rt.alertHisEventGet)
pages.GET("/event-notify-records/:eid", rt.notificationRecordList)
} else {
pages.GET("/alert-cur-event/:eid", rt.auth(), rt.user(), rt.alertCurEventGet)
pages.GET("/alert-his-event/:eid", rt.auth(), rt.user(), rt.alertHisEventGet)
pages.GET("/event-notify-records/:eid", rt.auth(), rt.user(), rt.notificationRecordList)
}
// card logic
@@ -555,7 +551,6 @@ func (rt *Router) Config(r *gin.Engine) {
service.GET("/targets-of-alert-rule", rt.targetsOfAlertRule)
service.POST("/notify-record", rt.notificationRecordAdd)
}
}

View File

@@ -222,11 +222,6 @@ func (rt *Router) alertCurEventGet(c *gin.Context) {
rt.bgroCheck(c, event.GroupId)
}
ruleConfig, needReset := models.FillRuleConfigTplName(rt.Ctx, event.RuleConfig)
if needReset {
event.RuleConfigJson = ruleConfig
}
ginx.NewRender(c).Data(event, nil)
}

View File

@@ -87,11 +87,6 @@ func (rt *Router) alertHisEventGet(c *gin.Context) {
rt.bgroCheck(c, event.GroupId)
}
ruleConfig, needReset := models.FillRuleConfigTplName(rt.Ctx, event.RuleConfig)
if needReset {
event.RuleConfigJson = ruleConfig
}
ginx.NewRender(c).Data(event, err)
}

View File

@@ -1,7 +1,6 @@
package router
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
@@ -126,25 +125,6 @@ func (rt *Router) alertRuleAddByImport(c *gin.Context) {
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) alertRuleAddByImportPromRule(c *gin.Context) {
username := c.MustGet("username").(string)
type PromRule struct {
Groups []models.PromRuleGroup `yaml:"groups"`
}
var pr PromRule
ginx.Dangerous(c.BindYAML(&pr))
if len(pr.Groups) == 0 {
ginx.Bomb(http.StatusBadRequest, "input yaml is empty")
}
lst := models.DealPromGroup(pr.Groups)
bgid := ginx.UrlParamInt64(c, "id")
err := rt.alertRuleAdd(lst, username, bgid, c.GetHeader("X-Language"))
ginx.NewRender(c).Data(err, nil)
}
func (rt *Router) alertRuleAddByService(c *gin.Context) {
var lst []models.AlertRule
ginx.BindJSON(c, &lst)
@@ -295,32 +275,6 @@ func (rt *Router) alertRulePutFields(c *gin.Context) {
continue
}
if f.Action == "annotations_add" {
if annotations, has := f.Fields["annotations"]; has {
annotationsMap := annotations.(map[string]interface{})
for k, v := range annotationsMap {
ar.AnnotationsJSON[k] = v.(string)
}
b, err := json.Marshal(ar.AnnotationsJSON)
ginx.Dangerous(err)
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, map[string]interface{}{"annotations": string(b)}))
continue
}
}
if f.Action == "annotations_del" {
if annotations, has := f.Fields["annotations"]; has {
annotationsKeys := annotations.(map[string]interface{})
for key := range annotationsKeys {
delete(ar.AnnotationsJSON, key)
}
b, err := json.Marshal(ar.AnnotationsJSON)
ginx.Dangerous(err)
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, map[string]interface{}{"annotations": string(b)}))
continue
}
}
if f.Action == "callback_add" {
// 增加一个 callback 地址
if callbacks, has := f.Fields["callbacks"]; has {

View File

@@ -3,7 +3,7 @@ package router
import (
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strings"
"time"
@@ -57,7 +57,7 @@ func HandleHeartbeat(c *gin.Context, ctx *ctx.Context, engineName string, metaSe
}
if req.Hostname == "" {
return req, errors.New("hostname is required")
return req, fmt.Errorf("hostname is required", 400)
}
// maybe from pushgw
@@ -121,10 +121,6 @@ func HandleHeartbeat(c *gin.Context, ctx *ctx.Context, engineName string, metaSe
field["agent_version"] = req.AgentVersion
}
if req.OS != "" && req.OS != target.OS {
field["os"] = req.OS
}
if len(field) > 0 {
err := target.UpdateFieldsMap(ctx, field)
if err != nil {

View File

@@ -1,205 +0,0 @@
package router
import (
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type NotificationResponse struct {
SubRules []SubRule `json:"sub_rules"`
Notifies map[string][]Record `json:"notifies"`
}
type SubRule struct {
SubID int64 `json:"sub_id"`
Notifies map[string][]Record `json:"notifies"`
}
type Notify struct {
Channel string `json:"channel"`
Records []Record `json:"records"`
}
type Record struct {
Target string `json:"target"`
Username string `json:"username"`
Status int `json:"status"`
Detail string `json:"detail"`
}
// notificationRecordAdd
func (rt *Router) notificationRecordAdd(c *gin.Context) {
var req models.NotificaitonRecord
ginx.BindJSON(c, &req)
err := req.Add(rt.Ctx)
ginx.NewRender(c).Data(req.Id, err)
}
func (rt *Router) notificationRecordList(c *gin.Context) {
eid := ginx.UrlParamInt64(c, "eid")
lst, err := models.NotificaitonRecordsGetByEventId(rt.Ctx, eid)
ginx.Dangerous(err)
response := buildNotificationResponse(rt.Ctx, lst)
ginx.NewRender(c).Data(response, nil)
}
func buildNotificationResponse(ctx *ctx.Context, nl []*models.NotificaitonRecord) NotificationResponse {
response := NotificationResponse{
SubRules: []SubRule{},
Notifies: make(map[string][]Record),
}
subRuleMap := make(map[int64]*SubRule)
// Collect all group IDs
groupIdSet := make(map[int64]struct{})
// map[SubId]map[Channel]map[Target]index
filter := make(map[int64]map[string]map[string]int)
for i, n := range nl {
// 对相同的 channel-target 进行合并
for _, gid := range n.GetGroupIds(ctx) {
groupIdSet[gid] = struct{}{}
}
if _, exists := filter[n.SubId]; !exists {
filter[n.SubId] = make(map[string]map[string]int)
}
if _, exists := filter[n.SubId][n.Channel]; !exists {
filter[n.SubId][n.Channel] = make(map[string]int)
}
idx, exists := filter[n.SubId][n.Channel][n.Target]
if !exists {
filter[n.SubId][n.Channel][n.Target] = i
} else {
if nl[idx].Status < n.Status {
nl[idx].Status = n.Status
}
nl[idx].Details = nl[idx].Details + ", " + n.Details
nl[i] = nil
}
}
// Fill usernames only once
usernameByTarget := fillUserNames(ctx, groupIdSet)
for _, n := range nl {
if n == nil {
continue
}
m := usernameByTarget[n.Target]
usernames := make([]string, 0, len(m))
for k := range m {
usernames = append(usernames, k)
}
if !checkChannel(n.Channel) {
// Hide sensitive information
n.Target = replaceLastEightChars(n.Target)
}
record := Record{
Target: n.Target,
Status: n.Status,
Detail: n.Details,
}
record.Username = strings.Join(usernames, ",")
if n.SubId > 0 {
// Handle SubRules
subRule, ok := subRuleMap[n.SubId]
if !ok {
newSubRule := &SubRule{
SubID: n.SubId,
}
newSubRule.Notifies = make(map[string][]Record)
newSubRule.Notifies[n.Channel] = []Record{record}
subRuleMap[n.SubId] = newSubRule
} else {
if _, exists := subRule.Notifies[n.Channel]; !exists {
subRule.Notifies[n.Channel] = []Record{record}
} else {
subRule.Notifies[n.Channel] = append(subRule.Notifies[n.Channel], record)
}
}
continue
}
if response.Notifies == nil {
response.Notifies = make(map[string][]Record)
}
if _, exists := response.Notifies[n.Channel]; !exists {
response.Notifies[n.Channel] = []Record{record}
} else {
response.Notifies[n.Channel] = append(response.Notifies[n.Channel], record)
}
}
for _, subRule := range subRuleMap {
response.SubRules = append(response.SubRules, *subRule)
}
return response
}
// check channel is one of the following: tx-sms, tx-voice, ali-sms, ali-voice, email, script
func checkChannel(channel string) bool {
switch channel {
case "tx-sms", "tx-voice", "ali-sms", "ali-voice", "email", "script":
return true
}
return false
}
func replaceLastEightChars(s string) string {
if len(s) <= 8 {
return strings.Repeat("*", len(s))
}
return s[:len(s)-8] + strings.Repeat("*", 8)
}
func fillUserNames(ctx *ctx.Context, groupIdSet map[int64]struct{}) map[string]map[string]struct{} {
userNameByTarget := make(map[string]map[string]struct{})
gids := make([]int64, 0, len(groupIdSet))
for gid := range groupIdSet {
gids = append(gids, gid)
}
users, err := models.UsersGetByGroupIds(ctx, gids)
if err != nil {
logger.Errorf("UsersGetByGroupIds failed, err: %v", err)
return userNameByTarget
}
for _, user := range users {
logger.Warningf("user: %s", user.Username)
for _, ch := range models.DefaultChannels {
target, exist := user.ExtractToken(ch)
if exist {
if _, ok := userNameByTarget[target]; !ok {
userNameByTarget[target] = make(map[string]struct{})
}
userNameByTarget[target][user.Username] = struct{}{}
}
}
}
return userNameByTarget
}

View File

@@ -182,7 +182,7 @@ func (rt *Router) notifyConfigPut(c *gin.Context) {
smtp, errSmtp := SmtpValidate(text)
ginx.Dangerous(errSmtp)
go sender.RestartEmailSender(rt.Ctx, smtp)
go sender.RestartEmailSender(smtp)
}
ginx.NewRender(c).Message(nil)

View File

@@ -196,13 +196,6 @@ func (rt *Router) taskTplDel(c *gin.Context) {
return
}
ids, err := models.GetAlertRuleIdsByTaskId(rt.Ctx, tid)
ginx.Dangerous(err)
if len(ids) > 0 {
ginx.NewRender(c).Message("can't del this task tpl, used by alert rule ids(%v) ", ids)
return
}
ginx.NewRender(c).Message(tpl.Del(rt.Ctx))
}

View File

@@ -1,7 +1,6 @@
package sso
import (
"fmt"
"log"
"time"
@@ -146,7 +145,7 @@ func Init(center cconf.Center, ctx *ctx.Context, configCache *memsto.ConfigCache
}
}
if configCache == nil {
log.Fatalln(fmt.Errorf("configCache is nil, sso initialization failed"))
logger.Error("configCache is nil, sso initialization failed")
}
ssoClient.configCache = configCache
userVariableMap := configCache.Get()

View File

@@ -1,41 +0,0 @@
package cron
import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/robfig/cron/v3"
"github.com/toolkits/pkg/logger"
)
func cleanNotifyRecord(ctx *ctx.Context, day int) {
lastWeek := time.Now().Unix() - 86400*int64(day)
err := models.DB(ctx).Model(&models.NotificaitonRecord{}).Where("created_at < ?", lastWeek).Delete(&models.NotificaitonRecord{}).Error
if err != nil {
logger.Errorf("Failed to clean notify record: %v", err)
}
}
// 每天凌晨1点执行清理任务
func CleanNotifyRecord(ctx *ctx.Context, day int) {
c := cron.New()
if day < 1 {
day = 7
}
// 使用cron表达式设置每天凌晨1点执行
_, err := c.AddFunc("0 1 * * *", func() {
cleanNotifyRecord(ctx, day)
})
if err != nil {
logger.Errorf("Failed to add clean notify record cron job: %v", err)
return
}
// 启动cron任务
c.Start()
}

View File

@@ -366,7 +366,6 @@ CREATE TABLE `target` (
`host_ip` varchar(15) default '' COMMENT 'IPv4 string',
`agent_version` varchar(255) default '' COMMENT 'agent version',
`engine_name` varchar(255) default '' COMMENT 'engine_name',
`os` VARCHAR(31) DEFAULT '' COMMENT 'os type',
`update_at` bigint not null default 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`ident`),
@@ -547,18 +546,6 @@ CREATE TABLE `builtin_payloads` (
KEY `idx_type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE notification_record (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`event_id` BIGINT NOT NULL,
`sub_id` BIGINT NOT NULL,
`channel` VARCHAR(255) NOT NULL,
`status` TINYINT NOT NULL DEFAULT 0,
`target` VARCHAR(1024) NOT NULL,
`details` VARCHAR(2048),
`created_at` BIGINT NOT NULL,
INDEX idx_evt (event_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `task_tpl`
(
`id` int unsigned NOT NULL AUTO_INCREMENT,

View File

@@ -83,20 +83,4 @@ ALTER TABLE recording_rule ADD COLUMN cron_pattern VARCHAR(255) DEFAULT '' COMME
/* v7.0.0-beta.14 */
ALTER TABLE alert_cur_event ADD COLUMN original_tags TEXT COMMENT 'labels key=val,,k2=v2';
ALTER TABLE alert_his_event ADD COLUMN original_tags TEXT COMMENT 'labels key=val,,k2=v2';
/* v7.1.0 */
ALTER TABLE target ADD COLUMN os VARCHAR(31) DEFAULT '' COMMENT 'os type';
/* v7.2.0 */
CREATE TABLE notification_record (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`event_id` BIGINT NOT NULL,
`sub_id` BIGINT NOT NULL,
`channel` VARCHAR(255) NOT NULL,
`status` TINYINT NOT NULL DEFAULT 0,
`target` VARCHAR(1024) NOT NULL,
`details` VARCHAR(2048),
`created_at` BIGINT NOT NULL,
INDEX idx_evt (event_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE alert_his_event ADD COLUMN original_tags TEXT COMMENT 'labels key=val,,k2=v2';

View File

@@ -192,7 +192,7 @@
"prom_ql": "",
"queries": [
{
"prom_ql": "elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_in_bytes * 100 \u003c 10",
"prom_ql": "elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_bytes * 100 \u003c 10",
"severity": 1
}
],
@@ -275,7 +275,7 @@
"prom_ql": "",
"queries": [
{
"prom_ql": "elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_in_bytes * 100 \u003c 20",
"prom_ql": "elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_bytes * 100 \u003c 20",
"severity": 2
}
],
@@ -1078,4 +1078,4 @@
"update_by": "",
"uuid": 1717556327360313000
}
]
]

View File

@@ -4,6 +4,7 @@ ElasticSearch 通过 HTTP JSON 的方式暴露了自身的监控指标,通过
如果是小规模集群,设置 `local=false`,从集群中某一个节点抓取数据,即可拿到整个集群所有节点的监控数据。如果是大规模集群,建议设置 `local=true`,在集群的每个节点上都部署抓取器,抓取本地 elasticsearch 进程的监控数据。
ElasticSearch 详细的监控讲解,请参考这篇 [文章](https://time.geekbang.org/column/article/628847)。
## 配置示例

View File

@@ -1,6 +1,6 @@
# kafka plugin
Kafka 的核心指标,其实都是通过 JMX 的方式暴露的。对于 JMX 暴露的指标,使用 jolokia 或者使用 jmx_exporter 那个 jar 包来采集即可,不需要本插件。
Kafka 的核心指标,其实都是通过 JMX 的方式暴露的,可以参考这篇 [文章](https://time.geekbang.org/column/article/628498)。对于 JMX 暴露的指标,使用 jolokia 或者使用 jmx_exporter 那个 jar 包来采集即可,不需要本插件。
本插件主要是采集的消费者延迟数据,这个数据无法通过 Kafka 服务端的 JMX 拿到。

View File

@@ -1,6 +1,6 @@
# Kubernetes
这个插件已经废弃。Kubernetes 监控系列可以参考这个 [文章](https://flashcat.cloud/categories/kubernetes%E7%9B%91%E6%8E%A7%E4%B8%93%E6%A0%8F/)。
这个插件已经废弃。Kubernetes 监控系列可以参考这个 [文章](https://flashcat.cloud/categories/kubernetes%E7%9B%91%E6%8E%A7%E4%B8%93%E6%A0%8F/)。或者参考 [专栏](https://time.geekbang.org/column/article/630306)。
不过 Kubernetes 这个类别下的内置告警规则和内置仪表盘都是可以使用的。

View File

@@ -305,12 +305,8 @@ func (e *AlertCurEvent) DB2FE() error {
e.CallbacksJSON = strings.Fields(e.Callbacks)
e.TagsJSON = strings.Split(e.Tags, ",,")
e.OriginalTagsJSON = strings.Split(e.OriginalTags, ",,")
if err := json.Unmarshal([]byte(e.Annotations), &e.AnnotationsJSON); err != nil {
return err
}
if err := json.Unmarshal([]byte(e.RuleConfig), &e.RuleConfigJson); err != nil {
return err
}
json.Unmarshal([]byte(e.Annotations), &e.AnnotationsJSON)
json.Unmarshal([]byte(e.RuleConfig), &e.RuleConfigJson)
return nil
}
@@ -350,34 +346,6 @@ func (e *AlertCurEvent) DB2Mem() {
}
}
func FillRuleConfigTplName(ctx *ctx.Context, ruleConfig string) (interface{}, bool) {
var config RuleConfig
err := json.Unmarshal([]byte(ruleConfig), &config)
if err != nil {
logger.Warningf("failed to unmarshal rule config: %v", err)
return nil, false
}
if len(config.TaskTpls) == 0 {
return nil, false
}
for i := 0; i < len(config.TaskTpls); i++ {
tpl, err := TaskTplGetById(ctx, config.TaskTpls[i].TplId)
if err != nil {
logger.Warningf("failed to get task tpl by id:%d, %v", config.TaskTpls[i].TplId, err)
return nil, false
}
if tpl == nil {
logger.Warningf("task tpl not found by id:%d", config.TaskTpls[i].TplId)
return nil, false
}
config.TaskTpls[i].TplName = tpl.Title
}
return config, true
}
// for webui
func (e *AlertCurEvent) FillNotifyGroups(ctx *ctx.Context, cache map[int64]*UserGroup) error {
// some user-group already deleted ?
@@ -621,8 +589,8 @@ func AlertCurEventGetMap(ctx *ctx.Context, cluster string) (map[int64]map[string
return ret, nil
}
func (e *AlertCurEvent) UpdateFieldsMap(ctx *ctx.Context, fields map[string]interface{}) error {
return DB(ctx).Model(e).Updates(fields).Error
func (m *AlertCurEvent) UpdateFieldsMap(ctx *ctx.Context, fields map[string]interface{}) error {
return DB(ctx).Model(m).Updates(fields).Error
}
func AlertCurEventUpgradeToV6(ctx *ctx.Context, dsm map[string]Datasource) error {

View File

@@ -117,10 +117,6 @@ func (e *AlertHisEvent) FillNotifyGroups(ctx *ctx.Context, cache map[int64]*User
return nil
}
// func (e *AlertHisEvent) FillTaskTplName(ctx *ctx.Context, cache map[int64]*UserGroup) error {
// }
func AlertHisEventTotal(ctx *ctx.Context, prods []string, bgids []int64, stime, etime int64, severity int, recovered int, dsIds []int64, cates []string, query string) (int64, error) {
session := DB(ctx).Model(&AlertHisEvent{}).Where("last_eval_time between ? and ?", stime, etime)

View File

@@ -26,21 +26,6 @@ const (
TDENGINE = "tdengine"
)
const (
AlertRuleEnabled = 0
AlertRuleDisabled = 1
AlertRuleEnableInGlobalBG = 0
AlertRuleEnableInOneBG = 1
AlertRuleNotNotifyRecovered = 0
AlertRuleNotifyRecovered = 1
AlertRuleNotifyRepeatStep60Min = 60
AlertRuleRecoverDuration0Sec = 0
)
type AlertRule struct {
Id int64 `json:"id" gorm:"primaryKey"`
GroupId int64 `json:"group_id"` // busi group id
@@ -99,24 +84,6 @@ type AlertRule struct {
UUID int64 `json:"uuid" gorm:"-"` // tpl identifier
}
type Tpl struct {
TplId int64 `json:"tpl_id"`
TplName string `json:"tpl_name"`
Host []string `json:"host"`
}
type RuleConfig struct {
Version string `json:"version,omitempty"`
EventRelabelConfig []*pconf.RelabelConfig `json:"event_relabel_config,omitempty"`
TaskTpls []*Tpl `json:"task_tpls,omitempty"`
Queries interface{} `json:"queries,omitempty"`
Triggers []Trigger `json:"triggers,omitempty"`
Inhibit bool `json:"inhibit,omitempty"`
PromQl string `json:"prom_ql,omitempty"`
Severity int `json:"severity,omitempty"`
AlgoParams interface{} `json:"algo_params,omitempty"`
}
type PromRuleConfig struct {
Queries []PromQuery `json:"queries"`
Inhibit bool `json:"inhibit"`
@@ -155,10 +122,6 @@ type Trigger struct {
Mode int `json:"mode"`
Exp string `json:"exp"`
Severity int `json:"severity"`
Type string `json:"type,omitempty"`
Duration int `json:"duration,omitempty"`
Percent int `json:"percent,omitempty"`
}
func GetHostsQuery(queries []HostQuery) []map[string]interface{} {
@@ -459,19 +422,6 @@ func (ar *AlertRule) UpdateColumn(ctx *ctx.Context, column string, value interfa
return DB(ctx).Model(ar).UpdateColumn("annotations", string(b)).Error
}
if column == "annotations" {
newAnnotations := value.(map[string]interface{})
ar.AnnotationsJSON = make(map[string]string)
for k, v := range newAnnotations {
ar.AnnotationsJSON[k] = v.(string)
}
b, err := json.Marshal(ar.AnnotationsJSON)
if err != nil {
return err
}
return DB(ctx).Model(ar).UpdateColumn("annotations", string(b)).Error
}
return DB(ctx).Model(ar).UpdateColumn(column, value).Error
}
@@ -729,25 +679,6 @@ func AlertRuleExists(ctx *ctx.Context, id, groupId int64, datasourceIds []int64,
return false, nil
}
func GetAlertRuleIdsByTaskId(ctx *ctx.Context, taskId int64) ([]int64, error) {
tpl := "%\"tpl_id\":" + fmt.Sprint(taskId) + "}%"
cb := "{ibex}/" + fmt.Sprint(taskId) + "%"
session := DB(ctx).Where("rule_config like ? or callbacks like ?", tpl, cb)
var lst []AlertRule
var ids []int64
err := session.Find(&lst).Error
if err != nil || len(lst) == 0 {
return ids, err
}
for i := 0; i < len(lst); i++ {
ids = append(ids, lst[i].Id)
}
return ids, nil
}
func AlertRuleGets(ctx *ctx.Context, groupId int64) ([]AlertRule, error) {
session := DB(ctx).Where("group_id=?", groupId).Order("name")

View File

@@ -58,7 +58,7 @@ func MigrateTables(db *gorm.DB) error {
dts := []interface{}{&RecordingRule{}, &AlertRule{}, &AlertSubscribe{}, &AlertMute{},
&TaskRecord{}, &ChartShare{}, &Target{}, &Configs{}, &Datasource{}, &NotifyTpl{},
&Board{}, &BoardBusigroup{}, &Users{}, &SsoConfig{}, &models.BuiltinMetric{},
&models.MetricFilter{}, &models.BuiltinComponent{}, &models.NotificaitonRecord{}}
&models.MetricFilter{}, &models.BuiltinComponent{}}
if !columnHasIndex(db, &AlertHisEvent{}, "original_tags") ||
!columnHasIndex(db, &AlertCurEvent{}, "original_tags") {
@@ -230,11 +230,10 @@ type Target struct {
HostIp string `gorm:"column:host_ip;varchar(15);default:'';comment:IPv4 string;index:idx_host_ip"`
AgentVersion string `gorm:"column:agent_version;varchar(255);default:'';comment:agent version;index:idx_agent_version"`
EngineName string `gorm:"column:engine_name;varchar(255);default:'';comment:engine name;index:idx_engine_name"`
OS string `gorm:"column:os;varchar(31);default:'';comment:os type;index:idx_os"`
}
type Datasource struct {
IsDefault bool `gorm:"column:is_default;type:boolean;comment:is default datasource"`
IsDefault bool `gorm:"column:is_default;type:boolean;not null;comment:is default datasource"`
}
type Configs struct {

View File

@@ -1,91 +0,0 @@
package models
import (
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
const (
NotiStatusSuccess = iota + 1
NotiStatusFailure
)
type NotificaitonRecord struct {
Id int64 `json:"id" gorm:"primaryKey;type:bigint;autoIncrement"`
EventId int64 `json:"event_id" gorm:"type:bigint;not null;index:idx_evt,priority:1;comment:event history id"`
SubId int64 `json:"sub_id" gorm:"type:bigint;comment:subscribed rule id"`
Channel string `json:"channel" gorm:"type:varchar(255);not null;comment:notification channel name"`
Status int `json:"status" gorm:"type:int;comment:notification status"` // 1-成功2-失败
Target string `json:"target" gorm:"type:varchar(1024);not null;comment:notification target"`
Details string `json:"details" gorm:"type:varchar(2048);default:'';comment:notification other info"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;comment:create time"`
}
func NewNotificationRecord(event *AlertCurEvent, channel, target string) *NotificaitonRecord {
return &NotificaitonRecord{
EventId: event.Id,
SubId: event.SubRuleId,
Channel: channel,
Status: NotiStatusSuccess,
Target: target,
}
}
func (n *NotificaitonRecord) SetStatus(status int) {
if n == nil {
return
}
n.Status = status
}
func (n *NotificaitonRecord) SetDetails(details string) {
if n == nil {
return
}
n.Details = details
}
func (n *NotificaitonRecord) TableName() string {
return "notification_record"
}
func (n *NotificaitonRecord) Add(ctx *ctx.Context) error {
return Insert(ctx, n)
}
func (n *NotificaitonRecord) GetGroupIds(ctx *ctx.Context) (groupIds []int64) {
if n == nil {
return
}
if n.SubId > 0 {
if sub, err := AlertSubscribeGet(ctx, "id=?", n.SubId); err != nil {
logger.Errorf("AlertSubscribeGet failed, err: %v", err)
} else {
groupIds = str.IdsInt64(sub.UserGroupIds, " ")
}
return
}
if event, err := AlertHisEventGetById(ctx, n.EventId); err != nil {
logger.Errorf("AlertHisEventGetById failed, err: %v", err)
} else {
groupIds = str.IdsInt64(event.NotifyGroups, " ")
}
return
}
func NotificaitonRecordsGetByEventId(ctx *ctx.Context, eid int64) ([]*NotificaitonRecord, error) {
return NotificaitonRecordsGet(ctx, "event_id=?", eid)
}
func NotificaitonRecordsGet(ctx *ctx.Context, where string, args ...interface{}) ([]*NotificaitonRecord, error) {
var lst []*NotificaitonRecord
err := DB(ctx).Where(where, args...).Find(&lst).Error
if err != nil {
return nil, err
}
return lst, nil
}

View File

@@ -1,93 +0,0 @@
package models
import (
"fmt"
"time"
"github.com/toolkits/pkg/logger"
)
type PromRule struct {
Alert string `yaml:"alert,omitempty" json:"alert,omitempty"` // 报警规则的名称
Record string `yaml:"record,omitempty" json:"record,omitempty"` // 记录规则的名称
Expr string `yaml:"expr,omitempty" json:"expr,omitempty"` // PromQL 表达式
For string `yaml:"for,omitempty" json:"for,omitempty"` // 告警的等待时间
Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"` // 规则的注释信息
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` // 规则的标签信息
}
type PromRuleGroup struct {
Name string `yaml:"name"`
Rules []PromRule `yaml:"rules"`
Interval string `yaml:"interval,omitempty"`
}
func convertInterval(interval string) int {
duration, err := time.ParseDuration(interval)
if err != nil {
logger.Errorf("Error parsing interval `%s`,err: %v", interval, err)
return 0
}
return int(duration.Seconds())
}
func ConvertAlert(rule PromRule, interval string) AlertRule {
annotations := rule.Annotations
appendTags := []string{}
severity := 2
if len(rule.Labels) > 0 {
for k, v := range rule.Labels {
if k != "severity" {
appendTags = append(appendTags, fmt.Sprintf("%s=%s", k, v))
} else {
switch v {
case "critical":
severity = 1
case "warning":
severity = 2
case "info":
severity = 3
}
}
}
}
return AlertRule{
Name: rule.Alert,
Severity: severity,
Disabled: AlertRuleEnabled,
PromForDuration: convertInterval(rule.For),
PromQl: rule.Expr,
PromEvalInterval: convertInterval(interval),
EnableStimeJSON: "00:00",
EnableEtimeJSON: "23:59",
EnableDaysOfWeekJSON: []string{
"1", "2", "3", "4", "5", "6", "0",
},
EnableInBG: AlertRuleEnableInGlobalBG,
NotifyRecovered: AlertRuleNotifyRecovered,
NotifyRepeatStep: AlertRuleNotifyRepeatStep60Min,
RecoverDuration: AlertRuleRecoverDuration0Sec,
AnnotationsJSON: annotations,
AppendTagsJSON: appendTags,
}
}
func DealPromGroup(promRule []PromRuleGroup) []AlertRule {
var alertRules []AlertRule
for _, group := range promRule {
interval := group.Interval
if interval == "" {
interval = "15s"
}
for _, rule := range group.Rules {
if rule.Alert != "" {
alertRules = append(alertRules, ConvertAlert(rule, interval))
}
}
}
return alertRules
}

View File

@@ -1,84 +0,0 @@
package models_test
import (
"testing"
"github.com/ccfos/nightingale/v6/models"
"gopkg.in/yaml.v2"
)
func TestConvertAlert(t *testing.T) {
jobMissing := []models.PromRule{}
err := yaml.Unmarshal([]byte(` - alert: PrometheusJobMissing
expr: absent(up{job="prometheus"})
for: 1m
labels:
severity: warning
annotations:
summary: Prometheus job missing (instance {{ $labels.instance }})
description: "A Prometheus job has disappeared\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"`), &jobMissing)
if err != nil {
t.Errorf("Failed to Unmarshal, err: %s", err)
}
t.Logf("jobMissing: %+v", jobMissing[0])
convJobMissing := models.ConvertAlert(jobMissing[0], "30s")
if convJobMissing.PromEvalInterval != 30 {
t.Errorf("PromEvalInterval is expected to be 30, but got %d",
convJobMissing.PromEvalInterval)
}
if convJobMissing.PromForDuration != 60 {
t.Errorf("PromForDuration is expected to be 60, but got %d",
convJobMissing.PromForDuration)
}
if convJobMissing.Severity != 2 {
t.Errorf("Severity is expected to be 2, but got %d", convJobMissing.Severity)
}
ruleEvaluationSlow := []models.PromRule{}
yaml.Unmarshal([]byte(` - alert: PrometheusRuleEvaluationSlow
expr: prometheus_rule_group_last_duration_seconds > prometheus_rule_group_interval_seconds
for: 180s
labels:
severity: info
annotations:
summary: Prometheus rule evaluation slow (instance {{ $labels.instance }})
description: "Prometheus rule evaluation took more time than the scheduled interval. It indicates a slower storage backend access or too complex query.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
`), &ruleEvaluationSlow)
t.Logf("ruleEvaluationSlow: %+v", ruleEvaluationSlow[0])
convRuleEvaluationSlow := models.ConvertAlert(ruleEvaluationSlow[0], "1m")
if convRuleEvaluationSlow.PromEvalInterval != 60 {
t.Errorf("PromEvalInterval is expected to be 60, but got %d",
convJobMissing.PromEvalInterval)
}
if convRuleEvaluationSlow.PromForDuration != 180 {
t.Errorf("PromForDuration is expected to be 180, but got %d",
convJobMissing.PromForDuration)
}
if convRuleEvaluationSlow.Severity != 3 {
t.Errorf("Severity is expected to be 3, but got %d", convJobMissing.Severity)
}
targetMissing := []models.PromRule{}
yaml.Unmarshal([]byte(` - alert: PrometheusTargetMissing
expr: up == 0
for: 1.5m
labels:
severity: critical
annotations:
summary: Prometheus target missing (instance {{ $labels.instance }})
description: "A Prometheus target has disappeared. An exporter might be crashed.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
`), &targetMissing)
t.Logf("targetMissing: %+v", targetMissing[0])
convTargetMissing := models.ConvertAlert(targetMissing[0], "1h")
if convTargetMissing.PromEvalInterval != 3600 {
t.Errorf("PromEvalInterval is expected to be 3600, but got %d",
convTargetMissing.PromEvalInterval)
}
if convTargetMissing.PromForDuration != 90 {
t.Errorf("PromForDuration is expected to be 90, but got %d",
convTargetMissing.PromForDuration)
}
if convTargetMissing.Severity != 1 {
t.Errorf("Severity is expected to be 1, but got %d", convTargetMissing.Severity)
}
}

View File

@@ -26,7 +26,6 @@ type Target struct {
HostIp string `json:"host_ip"` //ipv4do not needs range select
AgentVersion string `json:"agent_version"`
EngineName string `json:"engine_name"`
OS string `json:"os" gorm:"column:os"`
UnixTime int64 `json:"unixtime" gorm:"-"`
Offset int64 `json:"offset" gorm:"-"`
@@ -34,6 +33,7 @@ type Target struct {
MemUtil float64 `json:"mem_util" gorm:"-"`
CpuNum int `json:"cpu_num" gorm:"-"`
CpuUtil float64 `json:"cpu_util" gorm:"-"`
OS string `json:"os" gorm:"-"`
Arch string `json:"arch" gorm:"-"`
RemoteAddr string `json:"remote_addr" gorm:"-"`
}
@@ -111,8 +111,7 @@ func BuildTargetWhereWithQuery(query string) BuildTargetWhereOption {
arr := strings.Fields(query)
for i := 0; i < len(arr); i++ {
q := "%" + arr[i] + "%"
session = session.Where("ident like ? or note like ? or tags like ? "+
"or os like ?", q, q, q, q)
session = session.Where("ident like ? or note like ? or tags like ?", q, q, q)
}
}
return session
@@ -419,6 +418,7 @@ func (t *Target) FillMeta(meta *HostMeta) {
t.CpuNum = meta.CpuNum
t.UnixTime = meta.UnixTime
t.Offset = meta.Offset
t.OS = meta.OS
t.Arch = meta.Arch
t.RemoteAddr = meta.RemoteAddr
}

View File

@@ -304,22 +304,6 @@ func UserGetById(ctx *ctx.Context, id int64) (*User, error) {
return UserGet(ctx, "id=?", id)
}
func UsersGetByGroupIds(ctx *ctx.Context, groupIds []int64) ([]User, error) {
if len(groupIds) == 0 {
return nil, nil
}
userIds, err := GroupsMemberIds(ctx, groupIds)
if err != nil {
return nil, err
}
users, err := UserGetsByIds(ctx, userIds)
if err != nil {
return nil, err
}
return users, nil
}
func InitRoot(ctx *ctx.Context) {
user, err := UserGetByUsername(ctx, "root")
if err != nil {

View File

@@ -38,12 +38,6 @@ func MemberIds(ctx *ctx.Context, groupId int64) ([]int64, error) {
return ids, err
}
func GroupsMemberIds(ctx *ctx.Context, groupIds []int64) ([]int64, error) {
var ids []int64
err := DB(ctx).Model(&UserGroupMember{}).Where("group_id in ?", groupIds).Pluck("user_id", &ids).Error
return ids, err
}
func UserGroupMemberCount(ctx *ctx.Context, where string, args ...interface{}) (int64, error) {
return Count(DB(ctx).Model(&UserGroupMember{}).Where(where, args...))
}