-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new notify process #2464
base: notify-rule-feat
Are you sure you want to change the base?
new notify process #2464
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package dispatch | |
import ( | ||
"bytes" | ||
"encoding/json" | ||
"github.com/ccfos/nightingale/v6/pkg/tplx" | ||
"html/template" | ||
"net/url" | ||
"strconv" | ||
|
@@ -30,6 +31,10 @@ type Dispatch struct { | |
notifyConfigCache *memsto.NotifyConfigCacheType | ||
taskTplsCache *memsto.TaskTplCache | ||
|
||
notifyRuleCache *memsto.NotifyRuleCacheType | ||
notifyChannelCache *memsto.NotifyChannelCacheType | ||
messageTemplateCache *memsto.MessageTemplateCacheType | ||
|
||
alerting aconf.Alerting | ||
|
||
Senders map[string]sender.Sender | ||
|
@@ -47,15 +52,19 @@ type Dispatch struct { | |
// 创建一个 Notify 实例 | ||
func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType, | ||
alertSubscribeCache *memsto.AlertSubscribeCacheType, targetCache *memsto.TargetCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, | ||
taskTplsCache *memsto.TaskTplCache, alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch { | ||
taskTplsCache *memsto.TaskTplCache, notifyRuleCache *memsto.NotifyRuleCacheType, notifyChannelCache *memsto.NotifyChannelCacheType, | ||
messageTemplateCache *memsto.MessageTemplateCacheType, alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch { | ||
notify := &Dispatch{ | ||
alertRuleCache: alertRuleCache, | ||
userCache: userCache, | ||
userGroupCache: userGroupCache, | ||
alertSubscribeCache: alertSubscribeCache, | ||
targetCache: targetCache, | ||
notifyConfigCache: notifyConfigCache, | ||
taskTplsCache: taskTplsCache, | ||
alertRuleCache: alertRuleCache, | ||
userCache: userCache, | ||
userGroupCache: userGroupCache, | ||
alertSubscribeCache: alertSubscribeCache, | ||
targetCache: targetCache, | ||
notifyConfigCache: notifyConfigCache, | ||
taskTplsCache: taskTplsCache, | ||
notifyRuleCache: notifyRuleCache, | ||
notifyChannelCache: notifyChannelCache, | ||
messageTemplateCache: messageTemplateCache, | ||
|
||
alerting: alerting, | ||
|
||
|
@@ -131,6 +140,115 @@ func (e *Dispatch) relaodTpls() error { | |
return nil | ||
} | ||
|
||
func (e *Dispatch) HandleEventNotifyV2(event *models.AlertCurEvent, isSubscribe bool) { | ||
|
||
if len(event.NotifyRuleIDs) > 0 { | ||
for _, notifyRuleId := range event.NotifyRuleIDs { | ||
notifyRule := e.notifyRuleCache.Get(notifyRuleId) | ||
if notifyRule == nil { | ||
continue | ||
} | ||
|
||
for i := range notifyRule.NotifyConfigs { | ||
notifyChannel := e.notifyChannelCache.Get(notifyRule.NotifyConfigs[i].ChannelID) | ||
messageTemplate := e.messageTemplateCache.Get(notifyRule.NotifyConfigs[i].TemplateID) | ||
if notifyChannel == nil || messageTemplate == nil { | ||
continue | ||
} | ||
// todo go send | ||
// todo 聚合 event | ||
e.sendV2([]*models.AlertCurEvent{event}, ¬ifyRule.NotifyConfigs[i], notifyChannel, messageTemplate) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func (e *Dispatch) sendV2(events []*models.AlertCurEvent, notifyConfig *models.NotifyConfig, notifyChannel *models.NotifyChannelConfig, messageTemplate *models.MessageTemplate) { | ||
// event 内容渲染到 messageTemplate | ||
content := make(map[string]string) | ||
for key, msgTpl := range messageTemplate.Content { | ||
var defs = []string{ | ||
"{{$labels := .TagsMap}}", | ||
"{{$value := .TriggerValue}}", | ||
} | ||
text := strings.Join(append(defs, msgTpl), "") | ||
tpl, err := template.New(key).Funcs(tplx.TemplateFuncMap).Parse(text) | ||
if err != nil { | ||
continue | ||
} | ||
|
||
var body bytes.Buffer | ||
if err = tpl.Execute(&body, events); err != nil { | ||
continue | ||
} | ||
content[key] = body.String() | ||
} | ||
|
||
// notifyConfig 中配置的参数统一到 params 中供发送时替换使用 | ||
params := make([]map[string]string, 0) | ||
switch notifyChannel.ParamConfig.ParamType { | ||
case "user_info": | ||
if p, ok := notifyConfig.Params.(models.UserInfoParams); ok { | ||
users := e.userCache.GetByUserIds(p.UserIDs) | ||
userGroups := e.userGroupCache.GetByUserGroupIds(p.UserGroupIDs) | ||
var origin []string | ||
var val []string | ||
if notifyChannel.ParamConfig.UserInfo.ContactKey == "phone" { | ||
for _, user := range users { | ||
origin = append(origin, user.Phone) | ||
} | ||
for _, userGroup := range userGroups { | ||
for _, user := range userGroup.Users { | ||
origin = append(origin, user.Phone) | ||
} | ||
} | ||
|
||
} else if notifyChannel.ParamConfig.UserInfo.ContactKey == "email" { | ||
for _, user := range users { | ||
origin = append(origin, user.Email) | ||
} | ||
for _, userGroup := range userGroups { | ||
for _, user := range userGroup.Users { | ||
origin = append(origin, user.Email) | ||
} | ||
} | ||
} | ||
|
||
if notifyChannel.ParamConfig.BatchSend { | ||
val = append(val, strings.Join(origin, ",")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里直接使用逗号把联系方式拼接起来,后面灵活度不太够,可能有接口参数要求是以 ; 分割,或者是字符串数组 phone=["123...","334..."] |
||
} else { | ||
val = origin | ||
} | ||
|
||
for i := range val { | ||
param := make(map[string]string) | ||
param[notifyChannel.ParamConfig.UserInfo.ContactKey] = val[i] | ||
params = append(params, param) | ||
} | ||
} | ||
case "flashduty": | ||
|
||
case "custom": | ||
if p, ok := notifyConfig.Params.(models.CustomParams); ok { | ||
param := make(map[string]string) | ||
for k, v := range p { | ||
param[k] = v | ||
} | ||
params = append(params, param) | ||
} | ||
} | ||
|
||
switch notifyChannel.RequestType { | ||
case "http": | ||
notifyChannel.SendHTTP(content, params, e.notifyChannelCache.GetHttpClient(notifyChannel.ID)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里需要把返回的 err 信息打印出来,方便排查问题 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里需要把 events 也传进去,方便在 http 参数和 body 中使用,更灵活一些 |
||
case "email": | ||
notifyChannel.SendEmail(events, content, params, e.notifyChannelCache.GetSmtpClient(notifyChannel.ID)) | ||
case "script": | ||
notifyChannel.SendScript(events, content, params) | ||
default: | ||
} | ||
} | ||
|
||
// HandleEventNotify 处理event事件的主逻辑 | ||
// event: 告警/恢复事件 | ||
// isSubscribe: 告警事件是否由subscribe的配置产生 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package memsto | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/ccfos/nightingale/v6/dumper" | ||
"github.com/ccfos/nightingale/v6/models" | ||
"github.com/ccfos/nightingale/v6/pkg/ctx" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/toolkits/pkg/logger" | ||
) | ||
|
||
type MessageTemplateCacheType struct { | ||
statTotal int64 | ||
statLastUpdated int64 | ||
ctx *ctx.Context | ||
stats *Stats | ||
|
||
sync.RWMutex | ||
templates map[int64]*models.MessageTemplate // key: template id | ||
} | ||
|
||
func NewMessageTemplateCache(ctx *ctx.Context, stats *Stats) *MessageTemplateCacheType { | ||
mtc := &MessageTemplateCacheType{ | ||
statTotal: -1, | ||
statLastUpdated: -1, | ||
ctx: ctx, | ||
stats: stats, | ||
templates: make(map[int64]*models.MessageTemplate), | ||
} | ||
mtc.SyncMessageTemplates() | ||
return mtc | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) Reset() { | ||
mtc.Lock() | ||
defer mtc.Unlock() | ||
|
||
mtc.statTotal = -1 | ||
mtc.statLastUpdated = -1 | ||
mtc.templates = make(map[int64]*models.MessageTemplate) | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) StatChanged(total, lastUpdated int64) bool { | ||
if mtc.statTotal == total && mtc.statLastUpdated == lastUpdated { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) Set(m map[int64]*models.MessageTemplate, total, lastUpdated int64) { | ||
mtc.Lock() | ||
mtc.templates = m | ||
mtc.Unlock() | ||
|
||
// only one goroutine used, so no need lock | ||
mtc.statTotal = total | ||
mtc.statLastUpdated = lastUpdated | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) Get(templateId int64) *models.MessageTemplate { | ||
mtc.RLock() | ||
defer mtc.RUnlock() | ||
return mtc.templates[templateId] | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) GetTemplateIds() []int64 { | ||
mtc.RLock() | ||
defer mtc.RUnlock() | ||
|
||
count := len(mtc.templates) | ||
list := make([]int64, 0, count) | ||
for templateId := range mtc.templates { | ||
list = append(list, templateId) | ||
} | ||
|
||
return list | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) SyncMessageTemplates() { | ||
err := mtc.syncMessageTemplates() | ||
if err != nil { | ||
fmt.Println("failed to sync message templates:", err) | ||
exit(1) | ||
} | ||
|
||
go mtc.loopSyncMessageTemplates() | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) loopSyncMessageTemplates() { | ||
duration := time.Duration(9000) * time.Millisecond | ||
for { | ||
time.Sleep(duration) | ||
if err := mtc.syncMessageTemplates(); err != nil { | ||
logger.Warning("failed to sync message templates:", err) | ||
} | ||
} | ||
} | ||
|
||
func (mtc *MessageTemplateCacheType) syncMessageTemplates() error { | ||
start := time.Now() | ||
stat, err := models.MessageTemplateStatistics(mtc.ctx) | ||
if err != nil { | ||
dumper.PutSyncRecord("message_templates", start.Unix(), -1, -1, "failed to query statistics: "+err.Error()) | ||
return errors.WithMessage(err, "failed to exec MessageTemplateStatistics") | ||
} | ||
|
||
if !mtc.StatChanged(stat.Total, stat.LastUpdated) { | ||
mtc.stats.GaugeCronDuration.WithLabelValues("sync_message_templates").Set(0) | ||
mtc.stats.GaugeSyncNumber.WithLabelValues("sync_message_templates").Set(0) | ||
dumper.PutSyncRecord("message_templates", start.Unix(), -1, -1, "not changed") | ||
return nil | ||
} | ||
|
||
lst, err := models.MessageTemplateGetsAll(mtc.ctx) | ||
if err != nil { | ||
dumper.PutSyncRecord("message_templates", start.Unix(), -1, -1, "failed to query records: "+err.Error()) | ||
return errors.WithMessage(err, "failed to exec MessageTemplateGetsAll") | ||
} | ||
|
||
m := make(map[int64]*models.MessageTemplate) | ||
for i := 0; i < len(lst); i++ { | ||
m[lst[i].ID] = lst[i] | ||
} | ||
|
||
mtc.Set(m, stat.Total, stat.LastUpdated) | ||
|
||
ms := time.Since(start).Milliseconds() | ||
mtc.stats.GaugeCronDuration.WithLabelValues("sync_message_templates").Set(float64(ms)) | ||
mtc.stats.GaugeSyncNumber.WithLabelValues("sync_message_templates").Set(float64(len(m))) | ||
logger.Infof("timer: sync message templates done, cost: %dms, number: %d", ms, len(m)) | ||
dumper.PutSyncRecord("message_templates", start.Unix(), ms, len(m), "success") | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里缺少了对用户的其他联系方式的处理,比如公司内部 im,个人相关的 token