mirror of
https://github.com/ccfos/nightingale.git
synced 2026-03-06 16:08:56 +00:00
Compare commits
18 Commits
optimize-c
...
v8.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a098d5d39c | ||
|
|
05c3f1e0e4 | ||
|
|
d5740164f2 | ||
|
|
8c2383c410 | ||
|
|
9af024fb99 | ||
|
|
12f3cc21e1 | ||
|
|
0b3bb54eb4 | ||
|
|
da813e2b0c | ||
|
|
50fa2499b7 | ||
|
|
2c5ae5b3a9 | ||
|
|
522932aeb4 | ||
|
|
35ac0ddea5 | ||
|
|
26fa750309 | ||
|
|
1eba607aeb | ||
|
|
6aadd159af | ||
|
|
b6ad87523e | ||
|
|
ea5b6845de | ||
|
|
5ba5096da2 |
@@ -82,6 +82,10 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
|
||||
}
|
||||
|
||||
pipeline.Init()
|
||||
|
||||
// 设置通知记录回调函数
|
||||
notifyChannelCache.SetNotifyRecordFunc(sender.NotifyRecord)
|
||||
|
||||
return notify
|
||||
}
|
||||
|
||||
@@ -185,8 +189,8 @@ func (e *Dispatch) HandleEventWithNotifyRule(eventOrigin *models.AlertCurEvent)
|
||||
|
||||
for _, processor := range processors {
|
||||
logger.Infof("before processor notify_id: %d, event:%+v, processor:%+v", notifyRuleId, eventCopy, processor)
|
||||
eventCopy = processor.Process(e.ctx, eventCopy)
|
||||
logger.Infof("after processor notify_id: %d, event:%+v, processor:%+v", notifyRuleId, eventCopy, processor)
|
||||
eventCopy, res, err := processor.Process(e.ctx, eventCopy)
|
||||
logger.Infof("after processor notify_id: %d, event:%+v, processor:%+v, res:%v, err:%v", notifyRuleId, eventCopy, processor, res, err)
|
||||
if eventCopy == nil {
|
||||
logger.Warningf("notify_id: %d, event:%+v, processor:%+v, event is nil", notifyRuleId, eventCopy, processor)
|
||||
break
|
||||
@@ -447,41 +451,40 @@ func (e *Dispatch) sendV2(events []*models.AlertCurEvent, notifyRuleId int64, no
|
||||
}
|
||||
|
||||
for i := range flashDutyChannelIDs {
|
||||
start := time.Now()
|
||||
respBody, err := notifyChannel.SendFlashDuty(events, flashDutyChannelIDs[i], e.notifyChannelCache.GetHttpClient(notifyChannel.ID))
|
||||
respBody = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), respBody)
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, IntegrationUrl: %v dutychannel_id: %v, respBody: %v, err: %v", notifyRuleId, notifyChannel.Name, events[0], notifyChannel.RequestConfig.FlashDutyRequestConfig.IntegrationUrl, flashDutyChannelIDs[i], respBody, err)
|
||||
sender.NotifyRecord(e.ctx, events, notifyRuleId, notifyChannel.Name, strconv.FormatInt(flashDutyChannelIDs[i], 10), respBody, err)
|
||||
}
|
||||
return
|
||||
|
||||
case "http":
|
||||
if e.notifyChannelCache.HttpConcurrencyAdd(notifyChannel.ID) {
|
||||
defer e.notifyChannelCache.HttpConcurrencyDone(notifyChannel.ID)
|
||||
}
|
||||
if notifyChannel.RequestConfig == nil {
|
||||
logger.Warningf("notify_id: %d, channel_name: %v, event:%+v, request config not found", notifyRuleId, notifyChannel.Name, events[0])
|
||||
// 使用队列模式处理 http 通知
|
||||
// 创建通知任务
|
||||
task := &memsto.NotifyTask{
|
||||
Events: events,
|
||||
NotifyRuleId: notifyRuleId,
|
||||
NotifyChannel: notifyChannel,
|
||||
TplContent: tplContent,
|
||||
CustomParams: customParams,
|
||||
Sendtos: sendtos,
|
||||
}
|
||||
|
||||
if notifyChannel.RequestConfig.HTTPRequestConfig == nil {
|
||||
logger.Warningf("notify_id: %d, channel_name: %v, event:%+v, http request config not found", notifyRuleId, notifyChannel.Name, events[0])
|
||||
}
|
||||
|
||||
if NeedBatchContacts(notifyChannel.RequestConfig.HTTPRequestConfig) || len(sendtos) == 0 {
|
||||
resp, err := notifyChannel.SendHTTP(events, tplContent, customParams, sendtos, e.notifyChannelCache.GetHttpClient(notifyChannel.ID))
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, tplContent:%s, customParams:%v, userInfo:%+v, respBody: %v, err: %v", notifyRuleId, notifyChannel.Name, events[0], tplContent, customParams, sendtos, resp, err)
|
||||
|
||||
sender.NotifyRecord(e.ctx, events, notifyRuleId, notifyChannel.Name, getSendTarget(customParams, sendtos), resp, err)
|
||||
} else {
|
||||
for i := range sendtos {
|
||||
resp, err := notifyChannel.SendHTTP(events, tplContent, customParams, []string{sendtos[i]}, e.notifyChannelCache.GetHttpClient(notifyChannel.ID))
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, tplContent:%s, customParams:%v, userInfo:%+v, respBody: %v, err: %v", notifyRuleId, notifyChannel.Name, events[0], tplContent, customParams, sendtos[i], resp, err)
|
||||
sender.NotifyRecord(e.ctx, events, notifyRuleId, notifyChannel.Name, getSendTarget(customParams, []string{sendtos[i]}), resp, err)
|
||||
}
|
||||
// 将任务加入队列
|
||||
success := e.notifyChannelCache.EnqueueNotifyTask(task)
|
||||
if !success {
|
||||
logger.Errorf("failed to enqueue notify task for channel %d, notify_id: %d", notifyChannel.ID, notifyRuleId)
|
||||
// 如果入队失败,记录错误通知
|
||||
sender.NotifyRecord(e.ctx, events, notifyRuleId, notifyChannel.Name, getSendTarget(customParams, sendtos), "", errors.New("failed to enqueue notify task, queue is full"))
|
||||
}
|
||||
|
||||
case "smtp":
|
||||
notifyChannel.SendEmail(notifyRuleId, events, tplContent, sendtos, e.notifyChannelCache.GetSmtpClient(notifyChannel.ID))
|
||||
|
||||
case "script":
|
||||
start := time.Now()
|
||||
target, res, err := notifyChannel.SendScript(events, tplContent, customParams, sendtos)
|
||||
res = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res)
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, tplContent:%s, customParams:%v, target:%s, res:%s, err:%v", notifyRuleId, notifyChannel.Name, events[0], tplContent, customParams, target, res, err)
|
||||
sender.NotifyRecord(e.ctx, events, notifyRuleId, notifyChannel.Name, target, res, err)
|
||||
default:
|
||||
|
||||
@@ -144,14 +144,24 @@ func (arw *AlertRuleWorker) Start() {
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Eval() {
|
||||
logger.Infof("eval:%s started", arw.Key())
|
||||
begin := time.Now()
|
||||
var message string
|
||||
|
||||
defer func() {
|
||||
if len(message) == 0 {
|
||||
logger.Infof("rule_eval:%s finished, duration:%v", arw.Key(), time.Since(begin))
|
||||
} else {
|
||||
logger.Infof("rule_eval:%s finished, duration:%v, message:%s", arw.Key(), time.Since(begin), message)
|
||||
}
|
||||
}()
|
||||
|
||||
if arw.Processor.PromEvalInterval == 0 {
|
||||
arw.Processor.PromEvalInterval = getPromEvalInterval(arw.Processor.ScheduleEntry.Schedule)
|
||||
}
|
||||
|
||||
cachedRule := arw.Rule
|
||||
if cachedRule == nil {
|
||||
// logger.Errorf("rule_eval:%s Rule not found", arw.Key())
|
||||
message = "rule not found"
|
||||
return
|
||||
}
|
||||
arw.Processor.Stats.CounterRuleEval.WithLabelValues().Inc()
|
||||
@@ -177,11 +187,12 @@ func (arw *AlertRuleWorker) Eval() {
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s get anomaly point err:%s", arw.Key(), err.Error())
|
||||
message = "failed to get anomaly points"
|
||||
return
|
||||
}
|
||||
|
||||
if arw.Processor == nil {
|
||||
logger.Warningf("rule_eval:%s Processor is nil", arw.Key())
|
||||
message = "processor is nil"
|
||||
return
|
||||
}
|
||||
|
||||
@@ -223,7 +234,7 @@ func (arw *AlertRuleWorker) Eval() {
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Stop() {
|
||||
logger.Infof("rule_eval %s stopped", arw.Key())
|
||||
logger.Infof("rule_eval:%s stopped", arw.Key())
|
||||
close(arw.Quit)
|
||||
c := arw.Scheduler.Stop()
|
||||
<-c.Done()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -135,7 +136,8 @@ func EventMuteStrategy(event *models.AlertCurEvent, alertMuteCache *memsto.Alert
|
||||
}
|
||||
|
||||
for i := 0; i < len(mutes); i++ {
|
||||
if MatchMute(event, mutes[i]) {
|
||||
matched, _ := MatchMute(event, mutes[i])
|
||||
if matched {
|
||||
return true, mutes[i].Id
|
||||
}
|
||||
}
|
||||
@@ -144,9 +146,9 @@ func EventMuteStrategy(event *models.AlertCurEvent, alertMuteCache *memsto.Alert
|
||||
}
|
||||
|
||||
// MatchMute 如果传入了clock这个可选参数,就表示使用这个clock表示的时间,否则就从event的字段中取TriggerTime
|
||||
func MatchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int64) bool {
|
||||
func MatchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int64) (bool, error) {
|
||||
if mute.Disabled == 1 {
|
||||
return false
|
||||
return false, errors.New("mute is disabled")
|
||||
}
|
||||
|
||||
// 如果不是全局的,判断 匹配的 datasource id
|
||||
@@ -158,13 +160,13 @@ func MatchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
|
||||
// 判断 event.datasourceId 是否包含在 idm 中
|
||||
if _, has := idm[event.DatasourceId]; !has {
|
||||
return false
|
||||
return false, errors.New("datasource id not match")
|
||||
}
|
||||
}
|
||||
|
||||
if mute.MuteTimeType == models.TimeRange {
|
||||
if !mute.IsWithinTimeRange(event.TriggerTime) {
|
||||
return false
|
||||
return false, errors.New("event trigger time not within mute time range")
|
||||
}
|
||||
} else if mute.MuteTimeType == models.Periodic {
|
||||
ts := event.TriggerTime
|
||||
@@ -173,11 +175,11 @@ func MatchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
}
|
||||
|
||||
if !mute.IsWithinPeriodicMute(ts) {
|
||||
return false
|
||||
return false, errors.New("event trigger time not within periodic mute range")
|
||||
}
|
||||
} else {
|
||||
logger.Warningf("mute time type invalid, %d", mute.MuteTimeType)
|
||||
return false
|
||||
return false, errors.New("mute time type invalid")
|
||||
}
|
||||
|
||||
var matchSeverity bool
|
||||
@@ -193,12 +195,14 @@ func MatchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
}
|
||||
|
||||
if !matchSeverity {
|
||||
return false
|
||||
return false, errors.New("event severity not match mute severity")
|
||||
}
|
||||
|
||||
if mute.ITags == nil || len(mute.ITags) == 0 {
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return common.MatchTags(event.TagsMap, mute.ITags)
|
||||
if !common.MatchTags(event.TagsMap, mute.ITags) {
|
||||
return false, errors.New("event tags not match mute tags")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,26 +54,23 @@ func (c *AISummaryConfig) Init(settings interface{}) (models.Processor, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *AISummaryConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *models.AlertCurEvent {
|
||||
func (c *AISummaryConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) (*models.AlertCurEvent, string, error) {
|
||||
if c.Client == nil {
|
||||
if err := c.initHTTPClient(); err != nil {
|
||||
logger.Errorf("failed to initialize HTTP client: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to initialize HTTP client: %v processor: %v", err, c)
|
||||
}
|
||||
}
|
||||
|
||||
// 准备告警事件信息
|
||||
eventInfo, err := c.prepareEventInfo(event)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to prepare event info: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to prepare event info: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
// 调用AI模型生成总结
|
||||
summary, err := c.generateAISummary(eventInfo)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to generate AI summary: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to generate AI summary: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
// 将总结添加到annotations字段
|
||||
@@ -86,12 +82,11 @@ func (c *AISummaryConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent)
|
||||
// 更新Annotations字段
|
||||
b, err := json.Marshal(event.AnnotationsJSON)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to marshal annotations: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to marshal annotations: %v processor: %v", err, c)
|
||||
}
|
||||
event.Annotations = string(b)
|
||||
|
||||
return event
|
||||
return event, "", nil
|
||||
}
|
||||
|
||||
func (c *AISummaryConfig) initHTTPClient() error {
|
||||
@@ -137,7 +132,7 @@ func (c *AISummaryConfig) prepareEventInfo(event *models.AlertCurEvent) (string,
|
||||
func (c *AISummaryConfig) generateAISummary(eventInfo string) (string, error) {
|
||||
// 构建基础请求参数
|
||||
reqParams := map[string]interface{}{
|
||||
"model": c.ModelName,
|
||||
"model": c.ModelName,
|
||||
"messages": []Message{
|
||||
{
|
||||
Role: "user",
|
||||
|
||||
@@ -54,7 +54,8 @@ func TestAISummaryConfig_Process(t *testing.T) {
|
||||
assert.NotNil(t, processor)
|
||||
|
||||
// 测试处理函数
|
||||
result := processor.Process(&ctx.Context{}, event)
|
||||
result, _, err := processor.Process(&ctx.Context{}, event)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.NotEmpty(t, result.AnnotationsJSON["ai_summary"])
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package callback
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -42,7 +43,7 @@ func (c *CallbackConfig) Init(settings interface{}) (models.Processor, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *CallbackConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *models.AlertCurEvent {
|
||||
func (c *CallbackConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) (*models.AlertCurEvent, string, error) {
|
||||
if c.Client == nil {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipSSLVerify},
|
||||
@@ -51,7 +52,7 @@ func (c *CallbackConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent)
|
||||
if c.Proxy != "" {
|
||||
proxyURL, err := url.Parse(c.Proxy)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to parse proxy url: %v", err)
|
||||
return event, "", fmt.Errorf("failed to parse proxy url: %v processor: %v", err, c)
|
||||
} else {
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
@@ -71,14 +72,12 @@ func (c *CallbackConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent)
|
||||
|
||||
body, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to marshal event: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to marshal event: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", c.URL, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
logger.Errorf("failed to create request: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to create request: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
@@ -91,16 +90,14 @@ func (c *CallbackConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent)
|
||||
|
||||
resp, err := c.Client.Do(req)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to send request: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to send request: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to read response body: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to read response body: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
logger.Infof("response body: %s", string(b))
|
||||
return event
|
||||
logger.Debugf("callback processor response body: %s", string(b))
|
||||
return event, "callback success", nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package eventdrop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
texttemplate "text/template"
|
||||
|
||||
@@ -25,7 +26,7 @@ func (c *EventDropConfig) Init(settings interface{}) (models.Processor, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *EventDropConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *models.AlertCurEvent {
|
||||
func (c *EventDropConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) (*models.AlertCurEvent, string, error) {
|
||||
// 使用背景是可以根据此处理器,实现对事件进行更加灵活的过滤的逻辑
|
||||
// 在标签过滤和属性过滤都不满足需求时可以使用
|
||||
// 如果模板执行结果为 true,则删除该事件
|
||||
@@ -40,22 +41,20 @@ func (c *EventDropConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent)
|
||||
|
||||
tpl, err := texttemplate.New("eventdrop").Funcs(tplx.TemplateFuncMap).Parse(text)
|
||||
if err != nil {
|
||||
logger.Errorf("processor failed to parse template: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("processor failed to parse template: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
if err = tpl.Execute(&body, event); err != nil {
|
||||
logger.Errorf("processor failed to execute template: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("processor failed to execute template: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(body.String())
|
||||
logger.Infof("processor eventdrop result: %v", result)
|
||||
if result == "true" {
|
||||
logger.Infof("processor eventdrop drop event: %v", event)
|
||||
return nil
|
||||
return nil, "drop event success", nil
|
||||
}
|
||||
|
||||
return event
|
||||
return event, "drop event failed", nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package eventupdate
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -30,7 +31,7 @@ func (c *EventUpdateConfig) Init(settings interface{}) (models.Processor, error)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *EventUpdateConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *models.AlertCurEvent {
|
||||
func (c *EventUpdateConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) (*models.AlertCurEvent, string, error) {
|
||||
if c.Client == nil {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipSSLVerify},
|
||||
@@ -39,7 +40,7 @@ func (c *EventUpdateConfig) Process(ctx *ctx.Context, event *models.AlertCurEven
|
||||
if c.Proxy != "" {
|
||||
proxyURL, err := url.Parse(c.Proxy)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to parse proxy url: %v", err)
|
||||
return event, "", fmt.Errorf("failed to parse proxy url: %v processor: %v", err, c)
|
||||
} else {
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
@@ -59,14 +60,12 @@ func (c *EventUpdateConfig) Process(ctx *ctx.Context, event *models.AlertCurEven
|
||||
|
||||
body, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to marshal event: %v", err)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to marshal event: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", c.URL, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
logger.Errorf("failed to create request: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to create request: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
@@ -79,22 +78,19 @@ func (c *EventUpdateConfig) Process(ctx *ctx.Context, event *models.AlertCurEven
|
||||
|
||||
resp, err := c.Client.Do(req)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to send request: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to send request: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to read response body: %v event: %v", err, event)
|
||||
return event
|
||||
return nil, "", fmt.Errorf("failed to read response body: %v processor: %v", err, c)
|
||||
}
|
||||
logger.Infof("response body: %s", string(b))
|
||||
logger.Debugf("event update processor response body: %s", string(b))
|
||||
|
||||
err = json.Unmarshal(b, &event)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to unmarshal response body: %v event: %v", err, event)
|
||||
return event
|
||||
return event, "", fmt.Errorf("failed to unmarshal response body: %v processor: %v", err, c)
|
||||
}
|
||||
|
||||
return event
|
||||
return event, "", nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (r *RelabelConfig) Init(settings interface{}) (models.Processor, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (r *RelabelConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *models.AlertCurEvent {
|
||||
func (r *RelabelConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) (*models.AlertCurEvent, string, error) {
|
||||
sourceLabels := make([]model.LabelName, len(r.SourceLabels))
|
||||
for i := range r.SourceLabels {
|
||||
sourceLabels[i] = model.LabelName(strings.ReplaceAll(r.SourceLabels[i], ".", REPLACE_DOT))
|
||||
@@ -64,7 +64,7 @@ func (r *RelabelConfig) Process(ctx *ctx.Context, event *models.AlertCurEvent) *
|
||||
}
|
||||
|
||||
EventRelabel(event, relabelConfigs)
|
||||
return event
|
||||
return event, "", nil
|
||||
}
|
||||
|
||||
func EventRelabel(event *models.AlertCurEvent, relabelConfigs []*pconf.RelabelConfig) {
|
||||
|
||||
@@ -467,16 +467,18 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debugf("rule_eval:%s event:%+v fire", p.Key(), event)
|
||||
message := "unknown"
|
||||
defer func() {
|
||||
logger.Infof("rule_eval:%s event-hash-%s %s", p.Key(), event.Hash, message)
|
||||
}()
|
||||
|
||||
if fired, has := p.fires.Get(event.Hash); has {
|
||||
p.fires.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
|
||||
event.FirstTriggerTime = fired.FirstTriggerTime
|
||||
p.HandleFireEventHook(event)
|
||||
|
||||
if cachedRule.NotifyRepeatStep == 0 {
|
||||
logger.Debugf("rule_eval:%s event:%+v repeat is zero nothing to do", p.Key(), event)
|
||||
// 说明不想重复通知,那就直接返回了,nothing to do
|
||||
// do not need to send alert again
|
||||
message = "stalled, rule.notify_repeat_step is 0, no need to repeat notify"
|
||||
return
|
||||
}
|
||||
|
||||
@@ -485,21 +487,26 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
|
||||
if cachedRule.NotifyMaxNumber == 0 {
|
||||
// 最大可以发送次数如果是0,表示不想限制最大发送次数,一直发即可
|
||||
event.NotifyCurNumber = fired.NotifyCurNumber + 1
|
||||
message = fmt.Sprintf("fired, notify_repeat_step_matched(%d >= %d + %d * 60) notify_max_number_ignore(#%d / %d)", event.LastEvalTime, fired.LastSentTime, cachedRule.NotifyRepeatStep, event.NotifyCurNumber, cachedRule.NotifyMaxNumber)
|
||||
p.pushEventToQueue(event)
|
||||
} else {
|
||||
// 有最大发送次数的限制,就要看已经发了几次了,是否达到了最大发送次数
|
||||
if fired.NotifyCurNumber >= cachedRule.NotifyMaxNumber {
|
||||
logger.Debugf("rule_eval:%s event:%+v reach max number", p.Key(), event)
|
||||
message = fmt.Sprintf("stalled, notify_repeat_step_matched(%d >= %d + %d * 60) notify_max_number_not_matched(#%d / %d)", event.LastEvalTime, fired.LastSentTime, cachedRule.NotifyRepeatStep, fired.NotifyCurNumber, cachedRule.NotifyMaxNumber)
|
||||
return
|
||||
} else {
|
||||
event.NotifyCurNumber = fired.NotifyCurNumber + 1
|
||||
message = fmt.Sprintf("fired, notify_repeat_step_matched(%d >= %d + %d * 60) notify_max_number_matched(#%d / %d)", event.LastEvalTime, fired.LastSentTime, cachedRule.NotifyRepeatStep, event.NotifyCurNumber, cachedRule.NotifyMaxNumber)
|
||||
p.pushEventToQueue(event)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message = fmt.Sprintf("stalled, notify_repeat_step_not_matched(%d < %d + %d * 60)", event.LastEvalTime, fired.LastSentTime, cachedRule.NotifyRepeatStep)
|
||||
}
|
||||
} else {
|
||||
event.NotifyCurNumber = 1
|
||||
event.FirstTriggerTime = event.TriggerTime
|
||||
message = fmt.Sprintf("fired, first_trigger_time: %d", event.FirstTriggerTime)
|
||||
p.HandleFireEventHook(event)
|
||||
p.pushEventToQueue(event)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -134,7 +135,9 @@ func (c *DefaultCallBacker) CallBack(ctx CallBackContext) {
|
||||
|
||||
func doSendAndRecord(ctx *ctx.Context, url, token string, body interface{}, channel string,
|
||||
stats *astats.Stats, events []*models.AlertCurEvent) {
|
||||
start := time.Now()
|
||||
res, err := doSend(url, body, channel, stats)
|
||||
res = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res)
|
||||
NotifyRecord(ctx, events, 0, channel, token, res, err)
|
||||
}
|
||||
|
||||
@@ -166,7 +169,9 @@ func NotifyRecord(ctx *ctx.Context, evts []*models.AlertCurEvent, notifyRuleID i
|
||||
func doSend(url string, body interface{}, channel string, stats *astats.Stats) (string, error) {
|
||||
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
|
||||
|
||||
start := time.Now()
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
res = []byte(fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res))
|
||||
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()
|
||||
|
||||
@@ -79,6 +79,7 @@ func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
start := time.Now()
|
||||
err := startCmd(cmd)
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: run cmd err: %v", err)
|
||||
@@ -88,6 +89,7 @@ func alertingCallScript(ctx *ctx.Context, stdinBytes []byte, notifyScript models
|
||||
err, isTimeout := sys.WrapTimeout(cmd, time.Duration(config.Timeout)*time.Second)
|
||||
|
||||
res := buf.String()
|
||||
res = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res)
|
||||
|
||||
// 截断超出长度的输出
|
||||
if len(res) > 512 {
|
||||
|
||||
@@ -99,7 +99,9 @@ func SingleSendWebhooks(ctx *ctx.Context, webhooks map[string]*models.Webhook, e
|
||||
for _, conf := range webhooks {
|
||||
retryCount := 0
|
||||
for retryCount < 3 {
|
||||
start := time.Now()
|
||||
needRetry, res, err := sendWebhook(conf, event, stats)
|
||||
res = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res)
|
||||
NotifyRecord(ctx, []*models.AlertCurEvent{event}, 0, "webhook", conf.Url, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
@@ -169,7 +171,9 @@ func StartConsumer(ctx *ctx.Context, queue *WebhookQueue, popSize int, webhook *
|
||||
|
||||
retryCount := 0
|
||||
for retryCount < webhook.RetryCount {
|
||||
start := time.Now()
|
||||
needRetry, res, err := sendWebhook(webhook, events, stats)
|
||||
res = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), res)
|
||||
go NotifyRecord(ctx, events, 0, "webhook", webhook.Url, res, err)
|
||||
if !needRetry {
|
||||
break
|
||||
|
||||
@@ -254,6 +254,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
|
||||
pages.GET("/notify-channels", rt.notifyChannelsGets)
|
||||
pages.GET("/contact-keys", rt.contactKeysGets)
|
||||
pages.GET("/install-date", rt.installDateGet)
|
||||
|
||||
pages.GET("/self/perms", rt.auth(), rt.user(), rt.permsGets)
|
||||
pages.GET("/self/profile", rt.auth(), rt.user(), rt.selfProfileGet)
|
||||
@@ -372,6 +373,8 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/relabel-test", rt.auth(), rt.user(), rt.relabelTest)
|
||||
pages.POST("/busi-group/:id/alert-rules/clone", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.cloneToMachine)
|
||||
pages.POST("/busi-groups/alert-rules/clones", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.batchAlertRuleClone)
|
||||
pages.POST("/busi-group/alert-rules/notify-tryrun", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.alertRuleNotifyTryRun)
|
||||
pages.POST("/busi-group/alert-rules/enable-tryrun", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.alertRuleEnableTryRun)
|
||||
|
||||
pages.GET("/busi-groups/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGetsByGids)
|
||||
pages.GET("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGets)
|
||||
@@ -397,6 +400,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.bgrw(), rt.alertSubscribeAdd)
|
||||
pages.PUT("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/put"), rt.bgrw(), rt.alertSubscribePut)
|
||||
pages.DELETE("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/del"), rt.bgrw(), rt.alertSubscribeDel)
|
||||
pages.POST("/alert-subscribe/alert-subscribes-tryrun", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.alertSubscribeTryRun)
|
||||
|
||||
pages.GET("/alert-cur-event/:eid", rt.alertCurEventGet)
|
||||
pages.GET("/alert-his-event/:eid", rt.alertHisEventGet)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/mute"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/strx"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/pconf"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
@@ -157,6 +159,120 @@ func (rt *Router) alertRuleAddByFE(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
type AlertRuleTryRunForm struct {
|
||||
EventId int64 `json:"event_id" binding:"required"`
|
||||
AlertRuleConfig models.AlertRule `json:"config" binding:"required"`
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleNotifyTryRun(c *gin.Context) {
|
||||
// check notify channels of old version
|
||||
var f AlertRuleTryRunForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
hisEvent, err := models.AlertHisEventGetById(rt.Ctx, f.EventId)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if hisEvent == nil {
|
||||
ginx.Bomb(http.StatusNotFound, "event not found")
|
||||
}
|
||||
|
||||
curEvent := *hisEvent.ToCur()
|
||||
curEvent.SetTagsMap()
|
||||
|
||||
if f.AlertRuleConfig.NotifyVersion == 1 {
|
||||
for _, id := range f.AlertRuleConfig.NotifyRuleIds {
|
||||
notifyRule, err := models.GetNotifyRule(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
for _, notifyConfig := range notifyRule.NotifyConfigs {
|
||||
_, err = SendNotifyChannelMessage(rt.Ctx, rt.UserCache, rt.UserGroupCache, notifyConfig, []*models.AlertCurEvent{&curEvent})
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data("notification test ok", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.AlertRuleConfig.NotifyChannelsJSON) == 0 {
|
||||
ginx.Bomb(http.StatusOK, "no notify channels selected")
|
||||
}
|
||||
|
||||
if len(f.AlertRuleConfig.NotifyGroupsJSON) == 0 {
|
||||
ginx.Bomb(http.StatusOK, "no notify groups selected")
|
||||
}
|
||||
|
||||
ancs := make([]string, 0, len(curEvent.NotifyChannelsJSON))
|
||||
ugids := f.AlertRuleConfig.NotifyGroupsJSON
|
||||
ngids := make([]int64, 0)
|
||||
for i := 0; i < len(ugids); i++ {
|
||||
if gid, err := strconv.ParseInt(ugids[i], 10, 64); err == nil {
|
||||
ngids = append(ngids, gid)
|
||||
}
|
||||
}
|
||||
userGroups := rt.UserGroupCache.GetByUserGroupIds(ngids)
|
||||
uids := make([]int64, 0)
|
||||
for i := range userGroups {
|
||||
uids = append(uids, userGroups[i].UserIds...)
|
||||
}
|
||||
users := rt.UserCache.GetByUserIds(uids)
|
||||
for _, NotifyChannels := range curEvent.NotifyChannelsJSON {
|
||||
flag := true
|
||||
// ignore non-default channels
|
||||
switch NotifyChannels {
|
||||
case models.Dingtalk, models.Wecom, models.Feishu, models.Mm,
|
||||
models.Telegram, models.Email, models.FeishuCard:
|
||||
// do nothing
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// default channels
|
||||
for ui := range users {
|
||||
if _, b := users[ui].ExtractToken(NotifyChannels); b {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
ancs = append(ancs, NotifyChannels)
|
||||
}
|
||||
}
|
||||
if len(ancs) > 0 {
|
||||
ginx.Dangerous(errors.New(fmt.Sprintf("All users are missing notify channel configurations. Please check for missing tokens (each channel should be configured with at least one user). %v", ancs)))
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data("notification test ok", nil)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleEnableTryRun(c *gin.Context) {
|
||||
// check notify channels of old version
|
||||
var f AlertRuleTryRunForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
hisEvent, err := models.AlertHisEventGetById(rt.Ctx, f.EventId)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if hisEvent == nil {
|
||||
ginx.Bomb(http.StatusNotFound, "event not found")
|
||||
}
|
||||
|
||||
curEvent := *hisEvent.ToCur()
|
||||
curEvent.SetTagsMap()
|
||||
|
||||
if f.AlertRuleConfig.Disabled == 1 {
|
||||
ginx.Bomb(http.StatusOK, "rule is disabled")
|
||||
}
|
||||
|
||||
if mute.TimeSpanMuteStrategy(&f.AlertRuleConfig, &curEvent) {
|
||||
ginx.Bomb(http.StatusOK, "event is not match for period of time")
|
||||
}
|
||||
|
||||
if mute.BgNotMatchMuteStrategy(&f.AlertRuleConfig, &curEvent, rt.TargetCache) {
|
||||
ginx.Bomb(http.StatusOK, "event target busi group not match rule busi group")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data("event is effective", nil)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleAddByImport(c *gin.Context) {
|
||||
username := c.MustGet("username").(string)
|
||||
|
||||
|
||||
@@ -2,13 +2,17 @@ package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/common"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/strx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
)
|
||||
|
||||
// Return all, front-end search and paging
|
||||
@@ -104,6 +108,148 @@ func (rt *Router) alertSubscribeAdd(c *gin.Context) {
|
||||
ginx.NewRender(c).Message(f.Add(rt.Ctx))
|
||||
}
|
||||
|
||||
type SubscribeTryRunForm struct {
|
||||
EventId int64 `json:"event_id" binding:"required"`
|
||||
SubscribeConfig models.AlertSubscribe `json:"config" binding:"required"`
|
||||
}
|
||||
|
||||
func (rt *Router) alertSubscribeTryRun(c *gin.Context) {
|
||||
var f SubscribeTryRunForm
|
||||
ginx.BindJSON(c, &f)
|
||||
ginx.Dangerous(f.SubscribeConfig.Verify())
|
||||
|
||||
hisEvent, err := models.AlertHisEventGetById(rt.Ctx, f.EventId)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if hisEvent == nil {
|
||||
ginx.Bomb(http.StatusNotFound, "event not found")
|
||||
}
|
||||
|
||||
curEvent := *hisEvent.ToCur()
|
||||
curEvent.SetTagsMap()
|
||||
|
||||
lang := c.GetHeader("X-Language")
|
||||
|
||||
// 先判断匹配条件
|
||||
if !f.SubscribeConfig.MatchCluster(curEvent.DatasourceId) {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "event datasource not match"))
|
||||
}
|
||||
|
||||
if len(f.SubscribeConfig.RuleIds) != 0 {
|
||||
match := false
|
||||
for _, rid := range f.SubscribeConfig.RuleIds {
|
||||
if rid == curEvent.RuleId {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "event rule id not match"))
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配 tag
|
||||
f.SubscribeConfig.Parse()
|
||||
if !common.MatchTags(curEvent.TagsMap, f.SubscribeConfig.ITags) {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "event tags not match"))
|
||||
}
|
||||
|
||||
// 匹配group name
|
||||
if !common.MatchGroupsName(curEvent.GroupName, f.SubscribeConfig.IBusiGroups) {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "event group name not match"))
|
||||
}
|
||||
|
||||
// 检查严重级别(Severity)匹配
|
||||
if len(f.SubscribeConfig.SeveritiesJson) != 0 {
|
||||
match := false
|
||||
for _, s := range f.SubscribeConfig.SeveritiesJson {
|
||||
if s == curEvent.Severity || s == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "event severity not match"))
|
||||
}
|
||||
}
|
||||
|
||||
// 新版本通知规则
|
||||
if f.SubscribeConfig.NotifyVersion == 1 {
|
||||
if len(f.SubscribeConfig.NotifyRuleIds) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "no notify rules selected"))
|
||||
}
|
||||
|
||||
for _, id := range f.SubscribeConfig.NotifyRuleIds {
|
||||
notifyRule, err := models.GetNotifyRule(rt.Ctx, id)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusNotFound, i18n.Sprintf(lang, "subscribe notify rule not found: %v", err))
|
||||
}
|
||||
|
||||
for _, notifyConfig := range notifyRule.NotifyConfigs {
|
||||
_, err = SendNotifyChannelMessage(rt.Ctx, rt.UserCache, rt.UserGroupCache, notifyConfig, []*models.AlertCurEvent{&curEvent})
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "notify rule send error: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(i18n.Sprintf(lang, "event match subscribe and notification test ok"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 旧版通知方式
|
||||
f.SubscribeConfig.ModifyEvent(&curEvent)
|
||||
if len(curEvent.NotifyChannelsJSON) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "no notify channels selected"))
|
||||
}
|
||||
|
||||
if len(curEvent.NotifyGroupsJSON) == 0 {
|
||||
ginx.Bomb(http.StatusOK, i18n.Sprintf(lang, "no notify groups selected"))
|
||||
}
|
||||
|
||||
ancs := make([]string, 0, len(curEvent.NotifyChannelsJSON))
|
||||
ugids := strings.Fields(f.SubscribeConfig.UserGroupIds)
|
||||
ngids := make([]int64, 0)
|
||||
for i := 0; i < len(ugids); i++ {
|
||||
if gid, err := strconv.ParseInt(ugids[i], 10, 64); err == nil {
|
||||
ngids = append(ngids, gid)
|
||||
}
|
||||
}
|
||||
|
||||
userGroups := rt.UserGroupCache.GetByUserGroupIds(ngids)
|
||||
uids := make([]int64, 0)
|
||||
for i := range userGroups {
|
||||
uids = append(uids, userGroups[i].UserIds...)
|
||||
}
|
||||
users := rt.UserCache.GetByUserIds(uids)
|
||||
for _, NotifyChannels := range curEvent.NotifyChannelsJSON {
|
||||
flag := true
|
||||
// ignore non-default channels
|
||||
switch NotifyChannels {
|
||||
case models.Dingtalk, models.Wecom, models.Feishu, models.Mm,
|
||||
models.Telegram, models.Email, models.FeishuCard:
|
||||
// do nothing
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// default channels
|
||||
for ui := range users {
|
||||
if _, b := users[ui].ExtractToken(NotifyChannels); b {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
ancs = append(ancs, NotifyChannels)
|
||||
}
|
||||
}
|
||||
if len(ancs) > 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, i18n.Sprintf(lang, "all users missing notify channel configurations: %v", ancs))
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(i18n.Sprintf(lang, "event match subscribe and notify settings ok"), nil)
|
||||
}
|
||||
|
||||
func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
var fs []models.AlertSubscribe
|
||||
ginx.BindJSON(c, &fs)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
// 获取事件Pipeline列表
|
||||
@@ -145,13 +144,25 @@ func (rt *Router) tryRunEventPipeline(c *gin.Context) {
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "get processor: %+v err: %+v", p, err)
|
||||
}
|
||||
event = processor.Process(rt.Ctx, event)
|
||||
event, _, err = processor.Process(rt.Ctx, event)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "processor: %+v err: %+v", p, err)
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "event is dropped")
|
||||
ginx.NewRender(c).Data(map[string]interface{}{
|
||||
"event": event,
|
||||
"result": "event is dropped",
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(event, nil)
|
||||
m := map[string]interface{}{
|
||||
"event": event,
|
||||
"result": "",
|
||||
}
|
||||
ginx.NewRender(c).Data(m, nil)
|
||||
}
|
||||
|
||||
// 测试事件处理器
|
||||
@@ -170,15 +181,17 @@ func (rt *Router) tryRunEventProcessor(c *gin.Context) {
|
||||
|
||||
processor, err := models.GetProcessorByType(f.ProcessorConfig.Typ, f.ProcessorConfig.Config)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "get processor err: %+v", err)
|
||||
ginx.Bomb(200, "get processor err: %+v", err)
|
||||
}
|
||||
event = processor.Process(rt.Ctx, event)
|
||||
logger.Infof("processor %+v result: %+v", f.ProcessorConfig, event)
|
||||
if event == nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "event is dropped")
|
||||
event, res, err := processor.Process(rt.Ctx, event)
|
||||
if err != nil {
|
||||
ginx.Bomb(200, "processor err: %+v", err)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(event, nil)
|
||||
ginx.NewRender(c).Data(map[string]interface{}{
|
||||
"event": event,
|
||||
"result": res,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) tryRunEventProcessorByNotifyRule(c *gin.Context) {
|
||||
@@ -212,9 +225,17 @@ func (rt *Router) tryRunEventProcessorByNotifyRule(c *gin.Context) {
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "get processor: %+v err: %+v", p, err)
|
||||
}
|
||||
event = processor.Process(rt.Ctx, event)
|
||||
|
||||
event, _, err := processor.Process(rt.Ctx, event)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "processor: %+v err: %+v", p, err)
|
||||
}
|
||||
if event == nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "event is dropped")
|
||||
ginx.NewRender(c).Data(map[string]interface{}{
|
||||
"event": event,
|
||||
"result": "event is dropped",
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
)
|
||||
|
||||
// Return all, front-end search and paging
|
||||
@@ -71,14 +71,15 @@ func (rt *Router) alertMuteAdd(c *gin.Context) {
|
||||
}
|
||||
|
||||
type MuteTestForm struct {
|
||||
EventId int64 `json:"event_id" binding:"required"`
|
||||
AlertMute models.AlertMute `json:"mute_config" binding:"required"`
|
||||
EventId int64 `json:"event_id" binding:"required"`
|
||||
AlertMute models.AlertMute `json:"config" binding:"required"`
|
||||
PassTimeCheck bool `json:"pass_time_check"`
|
||||
}
|
||||
|
||||
func (rt *Router) alertMuteTryRun(c *gin.Context) {
|
||||
|
||||
var f MuteTestForm
|
||||
ginx.BindJSON(c, &f)
|
||||
ginx.Dangerous(f.AlertMute.Verify())
|
||||
|
||||
hisEvent, err := models.AlertHisEventGetById(rt.Ctx, f.EventId)
|
||||
ginx.Dangerous(err)
|
||||
@@ -90,18 +91,30 @@ func (rt *Router) alertMuteTryRun(c *gin.Context) {
|
||||
curEvent := *hisEvent.ToCur()
|
||||
curEvent.SetTagsMap()
|
||||
|
||||
// 绕过时间范围检查:设置时间范围为全量(0 到 int64 最大值),仅验证其他匹配条件(如标签、策略类型等)
|
||||
f.AlertMute.MuteTimeType = models.TimeRange
|
||||
f.AlertMute.Btime = 0 // 最小可能值(如 Unix 时间戳起点)
|
||||
f.AlertMute.Etime = math.MaxInt64 // 最大可能值(int64 上限)
|
||||
if f.PassTimeCheck {
|
||||
f.AlertMute.MuteTimeType = models.Periodic
|
||||
f.AlertMute.PeriodicMutesJson = []models.PeriodicMute{
|
||||
{
|
||||
EnableDaysOfWeek: "0 1 2 3 4 5 6",
|
||||
EnableStime: "00:00",
|
||||
EnableEtime: "00:00",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !mute.MatchMute(&curEvent, &f.AlertMute) {
|
||||
ginx.NewRender(c).Data("not match", nil)
|
||||
match, err := mute.MatchMute(&curEvent, &f.AlertMute)
|
||||
if err != nil {
|
||||
// 对错误信息进行 i18n 翻译
|
||||
translatedErr := i18n.Sprintf(c.GetHeader("X-Language"), err.Error())
|
||||
ginx.Bomb(http.StatusBadRequest, translatedErr)
|
||||
}
|
||||
|
||||
if !match {
|
||||
ginx.NewRender(c).Data("event not match mute", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data("mute test match", nil)
|
||||
|
||||
ginx.NewRender(c).Data("event match mute", nil)
|
||||
}
|
||||
|
||||
// Preview events (alert_cur_event) that match the mute strategy based on the following criteria:
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/dispatch"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/slice"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
@@ -161,94 +162,107 @@ func (rt *Router) notifyTest(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, "not events applicable")
|
||||
}
|
||||
|
||||
notifyChannels, err := models.NotifyChannelGets(rt.Ctx, f.NotifyConfig.ChannelID, "", "", -1)
|
||||
ginx.Dangerous(err)
|
||||
resp, err := SendNotifyChannelMessage(rt.Ctx, rt.UserCache, rt.UserGroupCache, f.NotifyConfig, events)
|
||||
ginx.NewRender(c).Data(resp, err)
|
||||
}
|
||||
|
||||
func SendNotifyChannelMessage(ctx *ctx.Context, userCache *memsto.UserCacheType, userGroup *memsto.UserGroupCacheType, notifyConfig models.NotifyConfig, events []*models.AlertCurEvent) (string, error) {
|
||||
notifyChannels, err := models.NotifyChannelGets(ctx, notifyConfig.ChannelID, "", "", -1)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get notify channels: %v", err)
|
||||
}
|
||||
|
||||
if len(notifyChannels) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "notify channel not found")
|
||||
return "", fmt.Errorf("notify channel not found")
|
||||
}
|
||||
|
||||
notifyChannel := notifyChannels[0]
|
||||
|
||||
if !notifyChannel.Enable {
|
||||
ginx.Bomb(http.StatusBadRequest, "notify channel not enabled, please enable it first")
|
||||
return "", fmt.Errorf("notify channel not enabled, please enable it first")
|
||||
}
|
||||
|
||||
tplContent := make(map[string]interface{})
|
||||
if notifyChannel.RequestType != "flashtudy" {
|
||||
messageTemplates, err := models.MessageTemplateGets(rt.Ctx, f.NotifyConfig.TemplateID, "", "")
|
||||
ginx.Dangerous(err)
|
||||
if notifyChannel.RequestType != "flashduty" {
|
||||
messageTemplates, err := models.MessageTemplateGets(ctx, notifyConfig.TemplateID, "", "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get message templates: %v", err)
|
||||
}
|
||||
|
||||
if len(messageTemplates) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "message template not found")
|
||||
return "", fmt.Errorf("message template not found")
|
||||
}
|
||||
tplContent = messageTemplates[0].RenderEvent(events)
|
||||
}
|
||||
|
||||
var contactKey string
|
||||
if notifyChannel.ParamConfig != nil && notifyChannel.ParamConfig.UserInfo != nil {
|
||||
contactKey = notifyChannel.ParamConfig.UserInfo.ContactKey
|
||||
}
|
||||
|
||||
sendtos, flashDutyChannelIDs, customParams := dispatch.GetNotifyConfigParams(&f.NotifyConfig, contactKey, rt.UserCache, rt.UserGroupCache)
|
||||
sendtos, flashDutyChannelIDs, customParams := dispatch.GetNotifyConfigParams(¬ifyConfig, contactKey, userCache, userGroup)
|
||||
|
||||
var resp string
|
||||
switch notifyChannel.RequestType {
|
||||
case "flashduty":
|
||||
client, err := models.GetHTTPClient(notifyChannel)
|
||||
ginx.Dangerous(err)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get http client: %v", err)
|
||||
}
|
||||
|
||||
for i := range flashDutyChannelIDs {
|
||||
resp, err = notifyChannel.SendFlashDuty(events, flashDutyChannelIDs[i], client)
|
||||
if err != nil {
|
||||
break
|
||||
return "", fmt.Errorf("failed to send flashduty notify: %v", err)
|
||||
}
|
||||
}
|
||||
logger.Infof("channel_name: %v, event:%+v, tplContent:%s, customParams:%v, respBody: %v, err: %v", notifyChannel.Name, events[0], tplContent, customParams, resp, err)
|
||||
ginx.NewRender(c).Data(resp, err)
|
||||
return resp, nil
|
||||
case "http":
|
||||
client, err := models.GetHTTPClient(notifyChannel)
|
||||
ginx.Dangerous(err)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get http client: %v", err)
|
||||
}
|
||||
|
||||
if notifyChannel.RequestConfig == nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "request config not found")
|
||||
return "", fmt.Errorf("request config is nil")
|
||||
}
|
||||
|
||||
if notifyChannel.RequestConfig.HTTPRequestConfig == nil {
|
||||
ginx.Bomb(http.StatusBadRequest, "http request config not found")
|
||||
return "", fmt.Errorf("http request config is nil")
|
||||
}
|
||||
|
||||
if dispatch.NeedBatchContacts(notifyChannel.RequestConfig.HTTPRequestConfig) || len(sendtos) == 0 {
|
||||
resp, err = notifyChannel.SendHTTP(events, tplContent, customParams, sendtos, client)
|
||||
logger.Infof("channel_name: %v, event:%+v, sendtos:%+v, tplContent:%s, customParams:%v, respBody: %v, err: %v", notifyChannel.Name, events[0], sendtos, tplContent, customParams, resp, err)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to send http notify: %v", err)
|
||||
return "", fmt.Errorf("failed to send http notify: %v", err)
|
||||
}
|
||||
ginx.NewRender(c).Data(resp, err)
|
||||
return resp, nil
|
||||
} else {
|
||||
for i := range sendtos {
|
||||
resp, err = notifyChannel.SendHTTP(events, tplContent, customParams, []string{sendtos[i]}, client)
|
||||
logger.Infof("channel_name: %v, event:%+v, tplContent:%s, customParams:%v, sendto:%+v, respBody: %v, err: %v", notifyChannel.Name, events[0], tplContent, customParams, sendtos[i], resp, err)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to send http notify: %v", err)
|
||||
ginx.NewRender(c).Message(err)
|
||||
return
|
||||
return "", fmt.Errorf("failed to send http notify: %v", err)
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Message(err)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
case "smtp":
|
||||
if len(sendtos) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "No valid email address in the user and team")
|
||||
return "", fmt.Errorf("no valid email address in the user and team")
|
||||
}
|
||||
err := notifyChannel.SendEmailNow(events, tplContent, sendtos)
|
||||
ginx.NewRender(c).Message(err)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send email notify: %v", err)
|
||||
}
|
||||
return resp, nil
|
||||
case "script":
|
||||
resp, _, err := notifyChannel.SendScript(events, tplContent, customParams, sendtos)
|
||||
logger.Infof("channel_name: %v, event:%+v, tplContent:%s, customParams:%v, respBody: %v, err: %v", notifyChannel.Name, events[0], tplContent, customParams, resp, err)
|
||||
ginx.NewRender(c).Data(resp, err)
|
||||
return resp, err
|
||||
default:
|
||||
logger.Errorf("unsupported request type: %v", notifyChannel.RequestType)
|
||||
ginx.NewRender(c).Message(errors.New("unsupported request type"))
|
||||
return "", fmt.Errorf("unsupported request type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,3 +235,20 @@ func (rt *Router) userDel(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Message(target.Del(rt.Ctx))
|
||||
}
|
||||
|
||||
func (rt *Router) installDateGet(c *gin.Context) {
|
||||
rootUser, err := models.UserGetByUsername(rt.Ctx, "root")
|
||||
if err != nil {
|
||||
logger.Errorf("get root user failed: %v", err)
|
||||
ginx.NewRender(c).Data(0, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if rootUser == nil {
|
||||
logger.Errorf("root user not found")
|
||||
ginx.NewRender(c).Data(0, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(rootUser.CreateAt, nil)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ func decryptConfig(config *ConfigType, cryptoKey string) error {
|
||||
|
||||
config.DB.DSN = decryptDsn
|
||||
|
||||
decryptRedisPwd, err := secu.DealWithDecrypt(config.Redis.Password, cryptoKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt the redis password: %s", err)
|
||||
}
|
||||
|
||||
config.Redis.Password = decryptRedisPwd
|
||||
|
||||
for k := range config.HTTP.APIForService.BasicAuth {
|
||||
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.APIForService.BasicAuth[k], cryptoKey)
|
||||
if err != nil {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -27,7 +27,7 @@ require (
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
|
||||
github.com/lib/pq v1.0.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -220,8 +220,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
|
||||
@@ -142,7 +142,6 @@ func (amc *AlertMuteCacheType) syncAlertMutes() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
amc.stats.GaugeCronDuration.WithLabelValues("sync_alert_mutes").Set(float64(ms))
|
||||
amc.stats.GaugeSyncNumber.WithLabelValues("sync_alert_mutes").Set(float64(len(lst)))
|
||||
logger.Infof("timer: sync mutes done, cost: %dms, number: %d", ms, len(lst))
|
||||
dumper.PutSyncRecord("alert_mutes", start.Unix(), ms, len(lst), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -132,7 +132,6 @@ func (arc *AlertRuleCacheType) syncAlertRules() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
arc.stats.GaugeCronDuration.WithLabelValues("sync_alert_rules").Set(float64(ms))
|
||||
arc.stats.GaugeSyncNumber.WithLabelValues("sync_alert_rules").Set(float64(len(m)))
|
||||
logger.Infof("timer: sync rules done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("alert_rules", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -180,7 +180,6 @@ func (c *AlertSubscribeCacheType) syncAlertSubscribes() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
c.stats.GaugeCronDuration.WithLabelValues("sync_alert_subscribes").Set(float64(ms))
|
||||
c.stats.GaugeSyncNumber.WithLabelValues("sync_alert_subscribes").Set(float64(len(lst)))
|
||||
logger.Infof("timer: sync subscribes done, cost: %dms, number: %d", ms, len(lst))
|
||||
dumper.PutSyncRecord("alert_subscribes", start.Unix(), ms, len(lst), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -118,8 +118,6 @@ func (c *BusiGroupCacheType) syncBusiGroups() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
c.stats.GaugeCronDuration.WithLabelValues("sync_busi_groups").Set(float64(ms))
|
||||
c.stats.GaugeSyncNumber.WithLabelValues("sync_busi_groups").Set(float64(len(m)))
|
||||
|
||||
logger.Infof("timer: sync busi groups done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("busi_groups", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -86,8 +86,6 @@ func (c *ConfigCache) syncConfigs() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
c.stats.GaugeCronDuration.WithLabelValues("sync_user_variables").Set(float64(ms))
|
||||
c.stats.GaugeSyncNumber.WithLabelValues("sync_user_variables").Set(float64(len(decryptMap)))
|
||||
|
||||
logger.Infof("timer: sync user_variables done, cost: %dms, number: %d", ms, len(decryptMap))
|
||||
dumper.PutSyncRecord("user_variables", start.Unix(), ms, len(decryptMap), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -82,8 +82,6 @@ func (c *CvalCache) syncConfigs() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
c.stats.GaugeCronDuration.WithLabelValues("sync_cvals").Set(float64(ms))
|
||||
c.stats.GaugeSyncNumber.WithLabelValues("sync_cvals").Set(float64(len(c.cvals)))
|
||||
|
||||
logger.Infof("timer: sync cvals done, cost: %dms", ms)
|
||||
dumper.PutSyncRecord("cvals", start.Unix(), ms, len(c.cvals), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -134,8 +134,6 @@ func (d *DatasourceCacheType) syncDatasources() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
d.stats.GaugeCronDuration.WithLabelValues("sync_datasources").Set(float64(ms))
|
||||
d.stats.GaugeSyncNumber.WithLabelValues("sync_datasources").Set(float64(len(ds)))
|
||||
|
||||
logger.Infof("timer: sync datasources done, cost: %dms, number: %d", ms, len(ds))
|
||||
dumper.PutSyncRecord("datasources", start.Unix(), ms, len(ds), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -156,7 +156,6 @@ func (epc *EventProcessorCacheType) syncEventProcessors() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
epc.stats.GaugeCronDuration.WithLabelValues("sync_event_processors").Set(float64(ms))
|
||||
epc.stats.GaugeSyncNumber.WithLabelValues("sync_event_processors").Set(float64(len(m)))
|
||||
logger.Infof("timer: sync event processors done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("event_processors", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -132,7 +132,6 @@ func (mtc *MessageTemplateCacheType) syncMessageTemplates() error {
|
||||
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
|
||||
|
||||
@@ -2,8 +2,10 @@ package memsto
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,9 +16,23 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/toolkits/pkg/container/list"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
// NotifyTask 表示一个通知发送任务
|
||||
type NotifyTask struct {
|
||||
Events []*models.AlertCurEvent
|
||||
NotifyRuleId int64
|
||||
NotifyChannel *models.NotifyChannelConfig
|
||||
TplContent map[string]interface{}
|
||||
CustomParams map[string]string
|
||||
Sendtos []string
|
||||
}
|
||||
|
||||
// NotifyRecordFunc 通知记录函数类型
|
||||
type NotifyRecordFunc func(ctx *ctx.Context, events []*models.AlertCurEvent, notifyRuleId int64, channelName, target, resp string, err error)
|
||||
|
||||
type NotifyChannelCacheType struct {
|
||||
statTotal int64
|
||||
statLastUpdated int64
|
||||
@@ -24,13 +40,18 @@ type NotifyChannelCacheType struct {
|
||||
stats *Stats
|
||||
|
||||
sync.RWMutex
|
||||
channels map[int64]*models.NotifyChannelConfig // key: channel id
|
||||
|
||||
httpConcurrency map[int64]chan struct{}
|
||||
channels map[int64]*models.NotifyChannelConfig // key: channel id
|
||||
channelsQueue map[int64]*list.SafeListLimited
|
||||
|
||||
httpClient map[int64]*http.Client
|
||||
smtpCh map[int64]chan *models.EmailContext
|
||||
smtpQuitCh map[int64]chan struct{}
|
||||
|
||||
// 队列消费者控制
|
||||
queueQuitCh map[int64]chan struct{}
|
||||
|
||||
// 通知记录回调函数
|
||||
notifyRecordFunc NotifyRecordFunc
|
||||
}
|
||||
|
||||
func NewNotifyChannelCache(ctx *ctx.Context, stats *Stats) *NotifyChannelCacheType {
|
||||
@@ -40,18 +61,20 @@ func NewNotifyChannelCache(ctx *ctx.Context, stats *Stats) *NotifyChannelCacheTy
|
||||
ctx: ctx,
|
||||
stats: stats,
|
||||
channels: make(map[int64]*models.NotifyChannelConfig),
|
||||
channelsQueue: make(map[int64]*list.SafeListLimited),
|
||||
queueQuitCh: make(map[int64]chan struct{}),
|
||||
httpClient: make(map[int64]*http.Client),
|
||||
smtpCh: make(map[int64]chan *models.EmailContext),
|
||||
smtpQuitCh: make(map[int64]chan struct{}),
|
||||
}
|
||||
|
||||
ncc.SyncNotifyChannels()
|
||||
return ncc
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) Reset() {
|
||||
ncc.Lock()
|
||||
defer ncc.Unlock()
|
||||
|
||||
ncc.statTotal = -1
|
||||
ncc.statLastUpdated = -1
|
||||
ncc.channels = make(map[int64]*models.NotifyChannelConfig)
|
||||
// SetNotifyRecordFunc 设置通知记录回调函数
|
||||
func (ncc *NotifyChannelCacheType) SetNotifyRecordFunc(fn NotifyRecordFunc) {
|
||||
ncc.notifyRecordFunc = fn
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) StatChanged(total, lastUpdated int64) bool {
|
||||
@@ -62,30 +85,257 @@ func (ncc *NotifyChannelCacheType) StatChanged(total, lastUpdated int64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) Set(m map[int64]*models.NotifyChannelConfig, httpConcurrency map[int64]chan struct{}, httpClient map[int64]*http.Client,
|
||||
smtpCh map[int64]chan *models.EmailContext, quitCh map[int64]chan struct{}, total, lastUpdated int64) {
|
||||
func (ncc *NotifyChannelCacheType) Set(m map[int64]*models.NotifyChannelConfig, total, lastUpdated int64) {
|
||||
ncc.Lock()
|
||||
for _, k := range ncc.httpConcurrency {
|
||||
close(k)
|
||||
}
|
||||
ncc.httpConcurrency = httpConcurrency
|
||||
ncc.channels = m
|
||||
ncc.httpClient = httpClient
|
||||
ncc.smtpCh = smtpCh
|
||||
defer ncc.Unlock()
|
||||
|
||||
for i := range ncc.smtpQuitCh {
|
||||
close(ncc.smtpQuitCh[i])
|
||||
}
|
||||
// 1. 处理需要删除的通道
|
||||
ncc.removeDeletedChannels(m)
|
||||
|
||||
ncc.smtpQuitCh = quitCh
|
||||
|
||||
ncc.Unlock()
|
||||
// 2. 处理新增和更新的通道
|
||||
ncc.addOrUpdateChannels(m)
|
||||
|
||||
// only one goroutine used, so no need lock
|
||||
ncc.statTotal = total
|
||||
ncc.statLastUpdated = lastUpdated
|
||||
}
|
||||
|
||||
// removeDeletedChannels 移除已删除的通道
|
||||
func (ncc *NotifyChannelCacheType) removeDeletedChannels(newChannels map[int64]*models.NotifyChannelConfig) {
|
||||
for chID := range ncc.channels {
|
||||
if _, exists := newChannels[chID]; !exists {
|
||||
logger.Infof("removing deleted channel %d", chID)
|
||||
|
||||
// 停止消费者协程
|
||||
if quitCh, exists := ncc.queueQuitCh[chID]; exists {
|
||||
close(quitCh)
|
||||
delete(ncc.queueQuitCh, chID)
|
||||
}
|
||||
|
||||
// 删除队列
|
||||
delete(ncc.channelsQueue, chID)
|
||||
|
||||
// 删除HTTP客户端
|
||||
delete(ncc.httpClient, chID)
|
||||
|
||||
// 停止SMTP发送器
|
||||
if quitCh, exists := ncc.smtpQuitCh[chID]; exists {
|
||||
close(quitCh)
|
||||
delete(ncc.smtpQuitCh, chID)
|
||||
delete(ncc.smtpCh, chID)
|
||||
}
|
||||
|
||||
// 删除通道配置
|
||||
delete(ncc.channels, chID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addOrUpdateChannels 添加或更新通道
|
||||
func (ncc *NotifyChannelCacheType) addOrUpdateChannels(newChannels map[int64]*models.NotifyChannelConfig) {
|
||||
for chID, newChannel := range newChannels {
|
||||
oldChannel, exists := ncc.channels[chID]
|
||||
if exists {
|
||||
if ncc.channelConfigChanged(oldChannel, newChannel) {
|
||||
logger.Infof("updating channel %d (new: %t)", chID, !exists)
|
||||
ncc.stopChannelResources(chID)
|
||||
} else {
|
||||
logger.Infof("channel %d config not changed", chID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 更新通道配置
|
||||
ncc.channels[chID] = newChannel
|
||||
|
||||
// 根据类型创建相应的资源
|
||||
switch newChannel.RequestType {
|
||||
case "http", "flashduty":
|
||||
// 创建HTTP客户端
|
||||
if newChannel.RequestConfig != nil && newChannel.RequestConfig.HTTPRequestConfig != nil {
|
||||
cli, err := models.GetHTTPClient(newChannel)
|
||||
if err != nil {
|
||||
logger.Warningf("failed to create HTTP client for channel %d: %v", chID, err)
|
||||
} else {
|
||||
if ncc.httpClient == nil {
|
||||
ncc.httpClient = make(map[int64]*http.Client)
|
||||
}
|
||||
ncc.httpClient[chID] = cli
|
||||
}
|
||||
}
|
||||
|
||||
// 对于 http 类型,启动队列和消费者
|
||||
if newChannel.RequestType == "http" {
|
||||
ncc.startHttpChannel(chID, newChannel)
|
||||
}
|
||||
case "smtp":
|
||||
// 创建SMTP发送器
|
||||
if newChannel.RequestConfig != nil && newChannel.RequestConfig.SMTPRequestConfig != nil {
|
||||
ch := make(chan *models.EmailContext)
|
||||
quit := make(chan struct{})
|
||||
go ncc.startEmailSender(chID, newChannel.RequestConfig.SMTPRequestConfig, ch, quit)
|
||||
|
||||
if ncc.smtpCh == nil {
|
||||
ncc.smtpCh = make(map[int64]chan *models.EmailContext)
|
||||
}
|
||||
if ncc.smtpQuitCh == nil {
|
||||
ncc.smtpQuitCh = make(map[int64]chan struct{})
|
||||
}
|
||||
ncc.smtpCh[chID] = ch
|
||||
ncc.smtpQuitCh[chID] = quit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// channelConfigChanged 检查通道配置是否发生变化
|
||||
func (ncc *NotifyChannelCacheType) channelConfigChanged(oldChannel, newChannel *models.NotifyChannelConfig) bool {
|
||||
if oldChannel == nil || newChannel == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// check updateat
|
||||
if oldChannel.UpdateAt != newChannel.UpdateAt {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// stopChannelResources 停止通道的相关资源
|
||||
func (ncc *NotifyChannelCacheType) stopChannelResources(chID int64) {
|
||||
// 停止HTTP消费者协程
|
||||
if quitCh, exists := ncc.queueQuitCh[chID]; exists {
|
||||
close(quitCh)
|
||||
delete(ncc.queueQuitCh, chID)
|
||||
delete(ncc.channelsQueue, chID)
|
||||
}
|
||||
|
||||
// 停止SMTP发送器
|
||||
if quitCh, exists := ncc.smtpQuitCh[chID]; exists {
|
||||
close(quitCh)
|
||||
delete(ncc.smtpQuitCh, chID)
|
||||
delete(ncc.smtpCh, chID)
|
||||
}
|
||||
}
|
||||
|
||||
// startHttpChannel 启动HTTP通道的队列和消费者
|
||||
func (ncc *NotifyChannelCacheType) startHttpChannel(chID int64, channel *models.NotifyChannelConfig) {
|
||||
if channel.RequestConfig == nil || channel.RequestConfig.HTTPRequestConfig == nil {
|
||||
logger.Warningf("notify channel %+v http request config not found", channel)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建队列
|
||||
queue := list.NewSafeListLimited(100000)
|
||||
ncc.channelsQueue[chID] = queue
|
||||
|
||||
// 启动消费者协程
|
||||
quitCh := make(chan struct{})
|
||||
ncc.queueQuitCh[chID] = quitCh
|
||||
|
||||
// 启动指定数量的消费者协程
|
||||
concurrency := channel.RequestConfig.HTTPRequestConfig.Concurrency
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go ncc.startNotifyConsumer(chID, queue, quitCh)
|
||||
}
|
||||
|
||||
logger.Infof("started %d notify consumers for channel %d", concurrency, chID)
|
||||
}
|
||||
|
||||
// 启动通知消费者协程
|
||||
func (ncc *NotifyChannelCacheType) startNotifyConsumer(channelID int64, queue *list.SafeListLimited, quitCh chan struct{}) {
|
||||
logger.Infof("starting notify consumer for channel %d", channelID)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-quitCh:
|
||||
logger.Infof("notify consumer for channel %d stopped", channelID)
|
||||
return
|
||||
default:
|
||||
// 从队列中取出任务
|
||||
task := queue.PopBack()
|
||||
if task == nil {
|
||||
// 队列为空,等待一段时间
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
notifyTask, ok := task.(*NotifyTask)
|
||||
if !ok {
|
||||
logger.Errorf("invalid task type in queue for channel %d", channelID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理通知任务
|
||||
ncc.processNotifyTask(notifyTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processNotifyTask 处理通知任务(仅处理 http 类型)
|
||||
func (ncc *NotifyChannelCacheType) processNotifyTask(task *NotifyTask) {
|
||||
httpClient := ncc.GetHttpClient(task.NotifyChannel.ID)
|
||||
|
||||
// 现在只处理 http 类型,flashduty 保持直接发送
|
||||
if task.NotifyChannel.RequestType == "http" {
|
||||
if len(task.Sendtos) == 0 || ncc.needBatchContacts(task.NotifyChannel.RequestConfig.HTTPRequestConfig) {
|
||||
start := time.Now()
|
||||
resp, err := task.NotifyChannel.SendHTTP(task.Events, task.TplContent, task.CustomParams, task.Sendtos, httpClient)
|
||||
resp = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), resp)
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, tplContent:%v, customParams:%v, userInfo:%+v, respBody: %v, err: %v",
|
||||
task.NotifyRuleId, task.NotifyChannel.Name, task.Events[0], task.TplContent, task.CustomParams, task.Sendtos, resp, err)
|
||||
|
||||
// 调用通知记录回调函数
|
||||
if ncc.notifyRecordFunc != nil {
|
||||
ncc.notifyRecordFunc(ncc.ctx, task.Events, task.NotifyRuleId, task.NotifyChannel.Name, ncc.getSendTarget(task.CustomParams, task.Sendtos), resp, err)
|
||||
}
|
||||
} else {
|
||||
for i := range task.Sendtos {
|
||||
start := time.Now()
|
||||
resp, err := task.NotifyChannel.SendHTTP(task.Events, task.TplContent, task.CustomParams, []string{task.Sendtos[i]}, httpClient)
|
||||
resp = fmt.Sprintf("duration: %d ms %s", time.Since(start).Milliseconds(), resp)
|
||||
logger.Infof("notify_id: %d, channel_name: %v, event:%+v, tplContent:%v, customParams:%v, userInfo:%+v, respBody: %v, err: %v",
|
||||
task.NotifyRuleId, task.NotifyChannel.Name, task.Events[0], task.TplContent, task.CustomParams, task.Sendtos[i], resp, err)
|
||||
|
||||
// 调用通知记录回调函数
|
||||
if ncc.notifyRecordFunc != nil {
|
||||
ncc.notifyRecordFunc(ncc.ctx, task.Events, task.NotifyRuleId, task.NotifyChannel.Name, ncc.getSendTarget(task.CustomParams, []string{task.Sendtos[i]}), resp, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否需要批量发送联系人
|
||||
func (ncc *NotifyChannelCacheType) needBatchContacts(requestConfig *models.HTTPRequestConfig) bool {
|
||||
if requestConfig == nil {
|
||||
return false
|
||||
}
|
||||
b, _ := json.Marshal(requestConfig)
|
||||
return strings.Contains(string(b), "$sendtos")
|
||||
}
|
||||
|
||||
// 获取发送目标
|
||||
func (ncc *NotifyChannelCacheType) getSendTarget(customParams map[string]string, sendtos []string) string {
|
||||
if len(customParams) == 0 {
|
||||
return strings.Join(sendtos, ",")
|
||||
}
|
||||
|
||||
values := make([]string, 0)
|
||||
for _, value := range customParams {
|
||||
runes := []rune(value)
|
||||
if len(runes) <= 4 {
|
||||
values = append(values, value)
|
||||
} else {
|
||||
maskedValue := string(runes[:len(runes)-4]) + "****"
|
||||
values = append(values, maskedValue)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(values, ",")
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) Get(channelId int64) *models.NotifyChannelConfig {
|
||||
ncc.RLock()
|
||||
defer ncc.RUnlock()
|
||||
@@ -117,6 +367,25 @@ func (ncc *NotifyChannelCacheType) GetChannelIds() []int64 {
|
||||
return list
|
||||
}
|
||||
|
||||
// 新增:将通知任务加入队列
|
||||
func (ncc *NotifyChannelCacheType) EnqueueNotifyTask(task *NotifyTask) bool {
|
||||
ncc.RLock()
|
||||
queue := ncc.channelsQueue[task.NotifyChannel.ID]
|
||||
ncc.RUnlock()
|
||||
|
||||
if queue == nil {
|
||||
logger.Errorf("no queue found for channel %d", task.NotifyChannel.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
success := queue.PushFront(task)
|
||||
if !success {
|
||||
logger.Warningf("failed to enqueue notify task for channel %d, queue is full", task.NotifyChannel.ID)
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) SyncNotifyChannels() {
|
||||
err := ncc.syncNotifyChannels()
|
||||
if err != nil {
|
||||
@@ -162,43 +431,12 @@ func (ncc *NotifyChannelCacheType) syncNotifyChannels() error {
|
||||
m[lst[i].ID] = lst[i]
|
||||
}
|
||||
|
||||
httpConcurrency := make(map[int64]chan struct{})
|
||||
httpClient := make(map[int64]*http.Client)
|
||||
smtpCh := make(map[int64]chan *models.EmailContext)
|
||||
quitCh := make(map[int64]chan struct{})
|
||||
|
||||
for i := range lst {
|
||||
// todo 优化变更粒度
|
||||
|
||||
switch lst[i].RequestType {
|
||||
case "http", "flashduty":
|
||||
if lst[i].RequestConfig == nil || lst[i].RequestConfig.HTTPRequestConfig == nil {
|
||||
logger.Warningf("notify channel %+v http request config not found", lst[i])
|
||||
continue
|
||||
}
|
||||
|
||||
cli, _ := models.GetHTTPClient(lst[i])
|
||||
httpClient[lst[i].ID] = cli
|
||||
httpConcurrency[lst[i].ID] = make(chan struct{}, lst[i].RequestConfig.HTTPRequestConfig.Concurrency)
|
||||
for j := 0; j < lst[i].RequestConfig.HTTPRequestConfig.Concurrency; j++ {
|
||||
httpConcurrency[lst[i].ID] <- struct{}{}
|
||||
}
|
||||
case "smtp":
|
||||
ch := make(chan *models.EmailContext)
|
||||
quit := make(chan struct{})
|
||||
go ncc.startEmailSender(lst[i].ID, lst[i].RequestConfig.SMTPRequestConfig, ch, quit)
|
||||
smtpCh[lst[i].ID] = ch
|
||||
quitCh[lst[i].ID] = quit
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
ncc.Set(m, httpConcurrency, httpClient, smtpCh, quitCh, stat.Total, stat.LastUpdated)
|
||||
// 增量更新:只传递通道配置,让增量更新逻辑按需创建资源
|
||||
ncc.Set(m, stat.Total, stat.LastUpdated)
|
||||
|
||||
ms := time.Since(start).Milliseconds()
|
||||
ncc.stats.GaugeCronDuration.WithLabelValues("sync_notify_channels").Set(float64(ms))
|
||||
ncc.stats.GaugeSyncNumber.WithLabelValues("sync_notify_channels").Set(float64(len(m)))
|
||||
logger.Infof("timer: sync notify channels done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("notify_channels", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
@@ -305,22 +543,3 @@ func (ncc *NotifyChannelCacheType) dialSmtp(quitCh chan struct{}, d *gomail.Dial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) HttpConcurrencyAdd(channelId int64) bool {
|
||||
ncc.RLock()
|
||||
defer ncc.RUnlock()
|
||||
if _, ok := ncc.httpConcurrency[channelId]; !ok {
|
||||
return false
|
||||
}
|
||||
_, ok := <-ncc.httpConcurrency[channelId]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ncc *NotifyChannelCacheType) HttpConcurrencyDone(channelId int64) {
|
||||
ncc.RLock()
|
||||
defer ncc.RUnlock()
|
||||
if _, ok := ncc.httpConcurrency[channelId]; !ok {
|
||||
return
|
||||
}
|
||||
ncc.httpConcurrency[channelId] <- struct{}{}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,6 @@ func (nrc *NotifyRuleCacheType) syncNotifyRules() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
nrc.stats.GaugeCronDuration.WithLabelValues("sync_notify_rules").Set(float64(ms))
|
||||
nrc.stats.GaugeSyncNumber.WithLabelValues("sync_notify_rules").Set(float64(len(m)))
|
||||
logger.Infof("timer: sync notify rules done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("notify_rules", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -133,7 +133,6 @@ func (rrc *RecordingRuleCacheType) syncRecordingRules() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
rrc.stats.GaugeCronDuration.WithLabelValues("sync_recording_rules").Set(float64(ms))
|
||||
rrc.stats.GaugeSyncNumber.WithLabelValues("sync_recording_rules").Set(float64(len(m)))
|
||||
logger.Infof("timer: sync recording rules done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("recording_rules", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -179,7 +179,6 @@ func (tc *TargetCacheType) syncTargets() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
tc.stats.GaugeCronDuration.WithLabelValues("sync_targets").Set(float64(ms))
|
||||
tc.stats.GaugeSyncNumber.WithLabelValues("sync_targets").Set(float64(len(lst)))
|
||||
logger.Infof("timer: sync targets done, cost: %dms, number: %d", ms, len(lst))
|
||||
dumper.PutSyncRecord("targets", start.Unix(), ms, len(lst), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -84,7 +84,6 @@ func (ttc *TaskTplCache) syncTaskTpl() error {
|
||||
ttc.Set(m, stat.Total, stat.LastUpdated)
|
||||
|
||||
ms := time.Since(start).Milliseconds()
|
||||
logger.Infof("timer: sync task tpls done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("task_tpls", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -189,8 +189,6 @@ func (uc *UserCacheType) syncUsers() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
uc.stats.GaugeCronDuration.WithLabelValues("sync_users").Set(float64(ms))
|
||||
uc.stats.GaugeSyncNumber.WithLabelValues("sync_users").Set(float64(len(m)))
|
||||
|
||||
logger.Infof("timer: sync users done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("users", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -158,8 +158,6 @@ func (ugc *UserGroupCacheType) syncUserGroups() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
ugc.stats.GaugeCronDuration.WithLabelValues("sync_user_groups").Set(float64(ms))
|
||||
ugc.stats.GaugeSyncNumber.WithLabelValues("sync_user_groups").Set(float64(len(m)))
|
||||
|
||||
logger.Infof("timer: sync user groups done, cost: %dms, number: %d", ms, len(m))
|
||||
dumper.PutSyncRecord("user_groups", start.Unix(), ms, len(m), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -168,8 +168,6 @@ func (utc *UserTokenCacheType) syncUserTokens() error {
|
||||
ms := time.Since(start).Milliseconds()
|
||||
utc.stats.GaugeCronDuration.WithLabelValues("sync_user_tokens").Set(float64(ms))
|
||||
utc.stats.GaugeSyncNumber.WithLabelValues("sync_user_tokens").Set(float64(len(tokenUsers)))
|
||||
|
||||
logger.Infof("timer: sync user tokens done, cost: %dms, number: %d", ms, len(tokenUsers))
|
||||
dumper.PutSyncRecord("user_tokens", start.Unix(), ms, len(tokenUsers), "success")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -132,6 +132,10 @@ func (s *AlertSubscribe) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if s.NotifyVersion == 1 && len(s.NotifyRuleIds) == 0 {
|
||||
return errors.New("no notify rules selected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type Processor interface {
|
||||
Init(settings interface{}) (Processor, error) // 初始化配置
|
||||
Process(ctx *ctx.Context, event *AlertCurEvent) *AlertCurEvent // 处理告警事件
|
||||
Init(settings interface{}) (Processor, error) // 初始化配置
|
||||
Process(ctx *ctx.Context, event *AlertCurEvent) (*AlertCurEvent, string, error)
|
||||
// 处理器有三种情况:
|
||||
// 1. 处理成功,返回处理后的事件
|
||||
// 2. 处理成功,不需要返回处理后端事件,只返回处理结果,将处理结果放到 string 中,比如 eventdrop callback 处理器
|
||||
// 3. 处理失败,返回错误,将错误放到 error 中
|
||||
}
|
||||
|
||||
type NewProcessorFn func(settings interface{}) (Processor, error)
|
||||
|
||||
@@ -242,7 +242,12 @@ var NewTplMap = map[string]string{
|
||||
- {{$key}}: {{$val}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
{{if $event.AnnotationsJSON}}
|
||||
- **附加信息**:
|
||||
{{- range $key, $val := $event.AnnotationsJSON}}
|
||||
- {{$key}}: {{$val}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
{{$domain := "http://127.0.0.1:17000" }}
|
||||
{{$mutelink := print $domain "/alert-mutes/add?busiGroup=" $event.GroupId "&cate=" $event.Cate "&datasource_ids=" $event.DatasourceId "&prod=" $event.RuleProd}}
|
||||
{{- range $key, $value := $event.TagsMap}}
|
||||
@@ -471,6 +476,10 @@ var NewTplMap = map[string]string{
|
||||
规则名称: {{$event.RuleName}}{{if $event.RuleNote}}
|
||||
规则备注: {{$event.RuleNote}}{{end}}
|
||||
监控指标: {{$event.TagsJSON}}
|
||||
附加信息:
|
||||
{{- range $key, $val := $event.AnnotationsJSON}}
|
||||
{{$key}}: {{$val}}
|
||||
{{- end}}
|
||||
{{if $event.IsRecovered}}恢复时间:{{timeformat $event.LastEvalTime}}{{else}}触发时间: {{timeformat $event.TriggerTime}}
|
||||
触发时值: {{$event.TriggerValue}}{{end}}
|
||||
发送时间: {{timestamp}}{{$domain := "http://127.0.0.1:17000" }}
|
||||
@@ -492,9 +501,15 @@ var NewTplMap = map[string]string{
|
||||
**事件标签:** {{$event.TagsJSON}}
|
||||
**触发时间:** {{timeformat $event.TriggerTime}}
|
||||
**发送时间:** {{timestamp}}
|
||||
**触发时值:** {{$event.TriggerValue}}
|
||||
**触发时值:** {{$event.TriggerValue}}
|
||||
{{if $event.RuleNote }}**告警描述:** **{{$event.RuleNote}}**{{end}}
|
||||
{{- end -}}
|
||||
{{if $event.AnnotationsJSON}}
|
||||
**附加信息**:
|
||||
{{- range $key, $val := $event.AnnotationsJSON}}
|
||||
{{$key}}: {{$val}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{$domain := "http://请联系管理员修改通知模板将域名替换为实际的域名" }}
|
||||
[事件详情]({{$domain}}/alert-his-events/{{$event.Id}})|[屏蔽1小时]({{$domain}}/alert-mutes/add?busiGroup={{$event.GroupId}}&cate={{$event.Cate}}&datasource_ids={{$event.DatasourceId}}&prod={{$event.RuleProd}}{{range $key, $value := $event.TagsMap}}&tags={{$key}}%3D{{$value}}{{end}})|[查看曲线]({{$domain}}/metric/explorer?data_source_id={{$event.DatasourceId}}&data_source_name=prometheus&mode=graph&prom_ql={{$event.PromQl|escape}})`,
|
||||
EmailSubject: `{{if $event.IsRecovered}}Recovered{{else}}Triggered{{end}}: {{$event.RuleName}} {{$event.TagsJSON}}`,
|
||||
@@ -518,7 +533,8 @@ var NewTplMap = map[string]string{
|
||||
**规则标题**: {{$event.RuleName}}{{if $event.RuleNote}}
|
||||
**规则备注**: {{$event.RuleNote}}{{end}}{{if $event.TargetIdent}}
|
||||
**监控对象**: {{$event.TargetIdent}}{{end}}
|
||||
**监控指标**: {{$event.TagsJSON}}{{if not $event.IsRecovered}}
|
||||
**监控指标**: {{$event.TagsJSON}}
|
||||
{{if $event.AnnotationsJSON}}**附加信息**:{{range $key, $val := $event.AnnotationsJSON}}{{$key}}:{{$val}} {{end}} {{end}}{{if not $event.IsRecovered}}
|
||||
**触发时值**: {{$event.TriggerValue}}{{end}}
|
||||
{{if $event.IsRecovered}}**恢复时间**: {{timeformat $event.LastEvalTime}}{{else}}**首次触发时间**: {{timeformat $event.FirstTriggerTime}}{{end}}
|
||||
{{$time_duration := sub now.Unix $event.FirstTriggerTime }}{{if $event.IsRecovered}}{{$time_duration = sub $event.LastEvalTime $event.FirstTriggerTime }}{{end}}**距离首次告警**: {{humanizeDurationInterface $time_duration}}
|
||||
|
||||
102
pkg/ginx/auth.go
Normal file
102
pkg/ginx/auth.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ginx
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||
const AuthUserKey = "user"
|
||||
|
||||
// Accounts defines a key/value for user/pass list of authorized logins.
|
||||
type Accounts []Account
|
||||
|
||||
type Account struct {
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
type authPair struct {
|
||||
value string
|
||||
user string
|
||||
}
|
||||
|
||||
type authPairs []authPair
|
||||
|
||||
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
if authValue == "" {
|
||||
return "", false
|
||||
}
|
||||
for _, pair := range a {
|
||||
if subtle.ConstantTimeCompare(StringToBytes(pair.value), StringToBytes(authValue)) == 1 {
|
||||
return pair.user, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
|
||||
// the key is the user name and the value is the password, as well as the name of the Realm.
|
||||
// If the realm is empty, "Authorization Required" will be used by default.
|
||||
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
|
||||
func BasicAuthForRealm(accounts Accounts, realm string) gin.HandlerFunc {
|
||||
if realm == "" {
|
||||
realm = "Authorization Required"
|
||||
}
|
||||
realm = "Basic realm=" + strconv.Quote(realm)
|
||||
pairs := processAccounts(accounts)
|
||||
return func(c *gin.Context) {
|
||||
// Search user in the slice of allowed credentials
|
||||
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization"))
|
||||
if !found {
|
||||
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||
c.Header("WWW-Authenticate", realm)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
|
||||
// c.MustGet(gin.AuthUserKey).
|
||||
c.Set(AuthUserKey, user)
|
||||
}
|
||||
}
|
||||
|
||||
// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
|
||||
// the key is the user name and the value is the password.
|
||||
func BasicAuth(accounts Accounts) gin.HandlerFunc {
|
||||
return BasicAuthForRealm(accounts, "")
|
||||
}
|
||||
|
||||
func processAccounts(accounts Accounts) authPairs {
|
||||
length := len(accounts)
|
||||
assert1(length > 0, "Empty list of authorized credentials")
|
||||
pairs := make(authPairs, 0, length)
|
||||
for _, account := range accounts {
|
||||
assert1(account.User != "", "User can not be empty")
|
||||
value := authorizationHeader(account.User, account.Password)
|
||||
pairs = append(pairs, authPair{
|
||||
value: value,
|
||||
user: account.User,
|
||||
})
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
func authorizationHeader(user, password string) string {
|
||||
base := user + ":" + password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString(StringToBytes(base))
|
||||
}
|
||||
|
||||
func assert1(guard bool, text string) {
|
||||
if !guard {
|
||||
panic(text)
|
||||
}
|
||||
}
|
||||
23
pkg/ginx/bytesconv.go
Normal file
23
pkg/ginx/bytesconv.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2023 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package ginx
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// StringToBytes converts string to byte slice without a memory allocation.
|
||||
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
|
||||
func StringToBytes(s string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
||||
|
||||
// BytesToString converts byte slice to string without a memory allocation.
|
||||
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
|
||||
func BytesToString(b []byte) string {
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
||||
@@ -51,6 +51,26 @@ var I18N = `{
|
||||
"builtin payload already exists": "内置模板已存在",
|
||||
"This functionality has not been enabled. Please contact the system administrator to activate it.": "此功能尚未启用。请联系系统管理员启用",
|
||||
"targets not exist: %s": "有些机器不存在: %s",
|
||||
"mute is disabled": "屏蔽规则已禁用",
|
||||
"datasource id not match": "数据源ID不匹配",
|
||||
"event trigger time not within mute time range": "事件触发时间不在屏蔽时间范围内",
|
||||
"event trigger time not within periodic mute range": "事件触发时间不在周期性屏蔽时间范围内",
|
||||
"mute time type invalid": "屏蔽时间类型无效",
|
||||
"event severity not match mute severity": "事件严重程度与屏蔽严重程度不匹配",
|
||||
"event tags not match mute tags": "事件标签与屏蔽标签不匹配",
|
||||
"event datasource not match": "事件数据源不匹配",
|
||||
"event rule id not match": "事件告警规则ID不匹配",
|
||||
"event tags not match": "事件标签不匹配",
|
||||
"event group name not match": "事件业务组名称不匹配",
|
||||
"event severity not match": "事件严重程度不匹配",
|
||||
"subscribe notify rule not found: %v": "订阅通知规则未找到: %v",
|
||||
"notify rule send error: %v": "通知规则发送错误: %v",
|
||||
"event match subscribe and notification test ok": "事件匹配订阅规则,通知测试成功",
|
||||
"no notify rules selected": "未选择通知规则",
|
||||
"no notify channels selected": "未选择通知渠道",
|
||||
"no notify groups selected": "未选择通知组",
|
||||
"all users missing notify channel configurations: %v": "所有用户缺少通知渠道配置: %v",
|
||||
"event match subscribe and notify settings ok": "事件匹配订阅规则,通知设置正常",
|
||||
|
||||
"Infrastructure": "基础设施",
|
||||
"Host - View": "机器 - 查看",
|
||||
@@ -215,6 +235,26 @@ var I18N = `{
|
||||
"AlertRule already exists": "告警規則已存在",
|
||||
"This functionality has not been enabled. Please contact the system administrator to activate it.": "此功能尚未啟用。請聯繫系統管理員啟用",
|
||||
"targets not exist: %s": "有些機器不存在: %s",
|
||||
"mute is disabled": "屏蔽規則已禁用",
|
||||
"datasource id not match": "數據源ID不匹配",
|
||||
"event trigger time not within mute time range": "事件觸發時間不在屏蔽時間範圍內",
|
||||
"event trigger time not within periodic mute range": "事件觸發時間不在週期性屏蔽時間範圍內",
|
||||
"mute time type invalid": "屏蔽時間類型無效",
|
||||
"event severity not match mute severity": "事件嚴重程度與屏蔽嚴重程度不匹配",
|
||||
"event tags not match mute tags": "事件標籤與屏蔽標籤不匹配",
|
||||
"event datasource not match": "事件數據源不匹配",
|
||||
"event rule id not match": "事件告警規則ID不匹配",
|
||||
"event tags not match": "事件標籤不匹配",
|
||||
"event group name not match": "事件業務組名稱不匹配",
|
||||
"event severity not match": "事件嚴重程度不匹配",
|
||||
"subscribe notify rule not found: %v": "訂閱通知規則未找到: %v",
|
||||
"notify rule send error: %v": "通知規則發送錯誤: %v",
|
||||
"event match subscribe and notification test ok": "事件匹配訂閱規則,通知測試成功",
|
||||
"no notify rules selected": "未選擇通知規則",
|
||||
"no notify channels selected": "未選擇通知渠道",
|
||||
"no notify groups selected": "未選擇通知組",
|
||||
"all users missing notify channel configurations: %v": "所有用戶缺少通知渠道配置: %v",
|
||||
"event match subscribe and notify settings ok": "事件匹配訂閱規則,通知設置正常",
|
||||
|
||||
"Infrastructure": "基礎設施",
|
||||
"Host - View": "機器 - 查看",
|
||||
@@ -376,6 +416,26 @@ var I18N = `{
|
||||
"builtin payload already exists": "ビルトインテンプレートは既に存在します",
|
||||
"This functionality has not been enabled. Please contact the system administrator to activate it.": "この機能はまだ有効になっていません。システム管理者に連絡して有効にしてください",
|
||||
"targets not exist: %s": "いくつかのマシンが存在しません: %s",
|
||||
"mute is disabled": "ミュートルールが無効になっています",
|
||||
"datasource id not match": "データソースIDが一致しません",
|
||||
"event trigger time not within mute time range": "イベントトリガー時間がミュート時間範囲内にありません",
|
||||
"event trigger time not within periodic mute range": "イベントトリガー時間が周期的ミュート時間範囲内にありません",
|
||||
"mute time type invalid": "ミュート時間タイプが無効です",
|
||||
"event severity not match mute severity": "イベントの重要度がミュートの重要度と一致しません",
|
||||
"event tags not match mute tags": "イベントタグがミュートタグと一致しません",
|
||||
"event datasource not match": "イベントデータソースが一致しません",
|
||||
"event rule id not match": "イベントアラートルールIDが一致しません",
|
||||
"event tags not match": "イベントタグが一致しません",
|
||||
"event group name not match": "イベントビジネスグループ名が一致しません",
|
||||
"event severity not match": "イベントの重要度が一致しません",
|
||||
"subscribe notify rule not found: %v": "サブスクライブ通知ルールが見つかりません: %v",
|
||||
"notify rule send error: %v": "通知ルール送信エラー: %v",
|
||||
"event match subscribe and notification test ok": "イベントがサブスクライブルールに一致し、通知テストが成功しました",
|
||||
"no notify rules selected": "通知ルールが選択されていません",
|
||||
"no notify channels selected": "通知チャンネルが選択されていません",
|
||||
"no notify groups selected": "通知グループが選択されていません",
|
||||
"all users missing notify channel configurations: %v": "すべてのユーザーに通知チャンネル設定がありません: %v",
|
||||
"event match subscribe and notify settings ok": "イベントがサブスクライブルールに一致し、通知設定が正常です",
|
||||
|
||||
"Infrastructure": "インフラストラクチャ",
|
||||
"Host - View": "機器 - 閲覧",
|
||||
@@ -537,6 +597,26 @@ var I18N = `{
|
||||
"builtin payload already exists": "Встроенный шаблон уже существует",
|
||||
"This functionality has not been enabled. Please contact the system administrator to activate it.": "Эта функция не активирована. Пожалуйста, обратитесь к системному администратору для активации",
|
||||
"targets not exist: %s": "Некоторые машины не существуют: %s",
|
||||
"mute is disabled": "Правило отключения оповещений деактивировано",
|
||||
"datasource id not match": "Идентификатор источника данных не совпадает",
|
||||
"event trigger time not within mute time range": "Время срабатывания события не входит в диапазон времени отключения оповещений",
|
||||
"event trigger time not within periodic mute range": "Время срабатывания события не входит в периодический диапазон отключения оповещений",
|
||||
"mute time type invalid": "Недопустимый тип времени отключения оповещений",
|
||||
"event severity not match mute severity": "Уровень важности события не соответствует уровню важности отключения оповещений",
|
||||
"event tags not match mute tags": "Теги события не соответствуют тегам отключения оповещений",
|
||||
"event datasource not match": "Источник данных события не соответствует",
|
||||
"event rule id not match": "Идентификатор правила оповещения события не соответствует",
|
||||
"event tags not match": "Теги события не соответствуют",
|
||||
"event group name not match": "Название бизнес-группы события не соответствует",
|
||||
"event severity not match": "Уровень важности события не соответствует",
|
||||
"subscribe notify rule not found: %v": "Правило уведомления подписки не найдено: %v",
|
||||
"notify rule send error: %v": "Ошибка отправки правила уведомления: %v",
|
||||
"event match subscribe and notification test ok": "Событие соответствует правилу подписки, тест уведомления успешен",
|
||||
"no notify rules selected": "Правила уведомлений не выбраны",
|
||||
"no notify channels selected": "Каналы уведомлений не выбраны",
|
||||
"no notify groups selected": "Группы уведомлений не выбраны",
|
||||
"all users missing notify channel configurations: %v": "У всех пользователей отсутствуют настройки каналов уведомлений: %v",
|
||||
"event match subscribe and notify settings ok": "Событие соответствует правилу подписки, настройки уведомлений в порядке",
|
||||
|
||||
"Infrastructure": "Инфраструктура",
|
||||
"Host - View": "Хост - Просмотр",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ginx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/pconf"
|
||||
@@ -86,15 +87,22 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
|
||||
if len(rt.HTTP.APIForAgent.BasicAuth) > 0 {
|
||||
// enable basic auth
|
||||
accounts := make(gin.Accounts)
|
||||
accounts := make(ginx.Accounts, 0)
|
||||
for username, password := range rt.HTTP.APIForAgent.BasicAuth {
|
||||
accounts[username] = password
|
||||
}
|
||||
for username, password := range rt.HTTP.APIForService.BasicAuth {
|
||||
accounts[username] = password
|
||||
accounts = append(accounts, ginx.Account{
|
||||
User: username,
|
||||
Password: password,
|
||||
})
|
||||
}
|
||||
|
||||
auth := gin.BasicAuth(accounts)
|
||||
for username, password := range rt.HTTP.APIForService.BasicAuth {
|
||||
accounts = append(accounts, ginx.Account{
|
||||
User: username,
|
||||
Password: password,
|
||||
})
|
||||
}
|
||||
|
||||
auth := ginx.BasicAuth(accounts)
|
||||
r.POST("/opentsdb/put", auth, rt.openTSDBPut)
|
||||
r.POST("/openfalcon/push", auth, rt.falconPush)
|
||||
r.POST("/prometheus/v1/write", auth, rt.remoteWrite)
|
||||
|
||||
Reference in New Issue
Block a user