mirror of https://github.com/grafana/grafana
feat(alerting): lots of progress on notifications, refactored them out to their own package, restored webhook notitication and added slack notification
parent
77c66a88d9
commit
ae5f8a76d9
@ -1,21 +0,0 @@ |
||||
package alerting |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
_ "github.com/grafana/grafana/pkg/tsdb/graphite" |
||||
) |
||||
|
||||
var ( |
||||
maxAlertExecutionRetries = 3 |
||||
) |
||||
|
||||
var engine *Engine |
||||
|
||||
func Init() { |
||||
if !setting.AlertingEnabled { |
||||
return |
||||
} |
||||
|
||||
engine = NewEngine() |
||||
engine.Start() |
||||
} |
@ -0,0 +1,19 @@ |
||||
package init |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
_ "github.com/grafana/grafana/pkg/tsdb/graphite" |
||||
) |
||||
|
||||
var engine *alerting.Engine |
||||
|
||||
func Init() { |
||||
if !setting.AlertingEnabled { |
||||
return |
||||
} |
||||
|
||||
engine = alerting.NewEngine() |
||||
engine.Start() |
||||
} |
@ -0,0 +1,10 @@ |
||||
package notifiers |
||||
|
||||
type NotifierBase struct { |
||||
Name string |
||||
Type string |
||||
} |
||||
|
||||
func (n *NotifierBase) GetType() string { |
||||
return n.Type |
||||
} |
@ -0,0 +1,20 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
func getRuleLink(rule *alerting.AlertRule) (string, error) { |
||||
slugQuery := &m.GetDashboardSlugByIdQuery{Id: rule.DashboardId} |
||||
if err := bus.Dispatch(slugQuery); err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
ruleLink := fmt.Sprintf("%sdashboard/db/%s?fullscreen&edit&tab=alert&panelId=%d", setting.AppUrl, slugQuery.Result, rule.PanelId) |
||||
return ruleLink, nil |
||||
} |
@ -0,0 +1,62 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/log" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
) |
||||
|
||||
func init() { |
||||
alerting.RegisterNotifier("email", NewEmailNotifier) |
||||
} |
||||
|
||||
type EmailNotifier struct { |
||||
NotifierBase |
||||
Addresses []string |
||||
log log.Logger |
||||
} |
||||
|
||||
func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) { |
||||
addressesString := model.Settings.Get("addresses").MustString() |
||||
|
||||
if addressesString == "" { |
||||
return nil, alerting.AlertValidationError{Reason: "Could not find addresses in settings"} |
||||
} |
||||
|
||||
return &EmailNotifier{ |
||||
NotifierBase: NotifierBase{ |
||||
Name: model.Name, |
||||
Type: model.Type, |
||||
}, |
||||
Addresses: strings.Split(addressesString, "\n"), |
||||
log: log.New("alerting.notifier.email"), |
||||
}, nil |
||||
} |
||||
|
||||
func (this *EmailNotifier) Notify(context *alerting.AlertResultContext) { |
||||
this.log.Info("Sending alert notification to", "addresses", this.Addresses) |
||||
|
||||
ruleLink, err := getRuleLink(context.Rule) |
||||
if err != nil { |
||||
this.log.Error("Failed get rule link", "error", err) |
||||
return |
||||
} |
||||
|
||||
cmd := &m.SendEmailCommand{ |
||||
Data: map[string]interface{}{ |
||||
"RuleState": context.Rule.State, |
||||
"RuleName": context.Rule.Name, |
||||
"Severity": context.Rule.Severity, |
||||
"RuleLink": ruleLink, |
||||
}, |
||||
To: this.Addresses, |
||||
Template: "alert_notification.html", |
||||
} |
||||
|
||||
if err := bus.Dispatch(cmd); err != nil { |
||||
this.log.Error("Failed to send alert notification email", "error", err) |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
func TestEmailNotifier(t *testing.T) { |
||||
Convey("Email notifier tests", t, func() { |
||||
|
||||
Convey("Parsing alert notification from settings", func() { |
||||
Convey("empty settings should return error", func() { |
||||
json := `{ }` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "ops", |
||||
Type: "email", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
_, err := NewEmailNotifier(model) |
||||
So(err, ShouldNotBeNil) |
||||
}) |
||||
|
||||
Convey("from settings", func() { |
||||
json := ` |
||||
{ |
||||
"addresses": "ops@grafana.org" |
||||
}` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "ops", |
||||
Type: "email", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
not, err := NewEmailNotifier(model) |
||||
emailNotifier := not.(*EmailNotifier) |
||||
|
||||
So(err, ShouldBeNil) |
||||
So(emailNotifier.Name, ShouldEqual, "ops") |
||||
So(emailNotifier.Type, ShouldEqual, "email") |
||||
So(emailNotifier.Addresses[0], ShouldEqual, "ops@grafana.org") |
||||
}) |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,66 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/log" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
) |
||||
|
||||
func init() { |
||||
alerting.RegisterNotifier("slack", NewSlackNotifier) |
||||
} |
||||
|
||||
func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) { |
||||
url := model.Settings.Get("url").MustString() |
||||
if url == "" { |
||||
return nil, alerting.AlertValidationError{Reason: "Could not find url property in settings"} |
||||
} |
||||
|
||||
return &SlackNotifier{ |
||||
NotifierBase: NotifierBase{ |
||||
Name: model.Name, |
||||
Type: model.Type, |
||||
}, |
||||
Url: url, |
||||
log: log.New("alerting.notifier.slack"), |
||||
}, nil |
||||
} |
||||
|
||||
type SlackNotifier struct { |
||||
NotifierBase |
||||
Url string |
||||
log log.Logger |
||||
} |
||||
|
||||
func (this *SlackNotifier) Notify(context *alerting.AlertResultContext) { |
||||
this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name) |
||||
|
||||
rule := context.Rule |
||||
|
||||
ruleLink, err := getRuleLink(rule) |
||||
if err != nil { |
||||
this.log.Error("Failed get rule link", "error", err) |
||||
return |
||||
} |
||||
|
||||
stateText := string(rule.Severity) |
||||
if !context.Firing { |
||||
stateText = "ok" |
||||
} |
||||
|
||||
text := fmt.Sprintf("[%s]: <%s|%s>", stateText, ruleLink, rule.Name) |
||||
|
||||
body := simplejson.New() |
||||
body.Set("text", text) |
||||
|
||||
data, _ := body.MarshalJSON() |
||||
cmd := &m.SendWebhook{Url: this.Url, Body: string(data)} |
||||
|
||||
if err := bus.Dispatch(cmd); err != nil { |
||||
this.log.Error("Failed to send slack notification", "error", err, "webhook", this.Name) |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/log" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
) |
||||
|
||||
func init() { |
||||
alerting.RegisterNotifier("webhook", NewWebHookNotifier) |
||||
} |
||||
|
||||
func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) { |
||||
url := model.Settings.Get("url").MustString() |
||||
if url == "" { |
||||
return nil, alerting.AlertValidationError{Reason: "Could not find url property in settings"} |
||||
} |
||||
|
||||
return &WebhookNotifier{ |
||||
NotifierBase: NotifierBase{ |
||||
Name: model.Name, |
||||
Type: model.Type, |
||||
}, |
||||
Url: url, |
||||
User: model.Settings.Get("user").MustString(), |
||||
Password: model.Settings.Get("password").MustString(), |
||||
log: log.New("alerting.notifier.webhook"), |
||||
}, nil |
||||
} |
||||
|
||||
type WebhookNotifier struct { |
||||
NotifierBase |
||||
Url string |
||||
User string |
||||
Password string |
||||
log log.Logger |
||||
} |
||||
|
||||
func (this *WebhookNotifier) Notify(context *alerting.AlertResultContext) { |
||||
this.log.Info("Sending webhook") |
||||
|
||||
bodyJSON := simplejson.New() |
||||
bodyJSON.Set("name", context.Rule.Name) |
||||
bodyJSON.Set("firing", context.Firing) |
||||
bodyJSON.Set("severity", context.Rule.Severity) |
||||
|
||||
body, _ := bodyJSON.MarshalJSON() |
||||
|
||||
cmd := &m.SendWebhook{ |
||||
Url: this.Url, |
||||
User: this.User, |
||||
Password: this.Password, |
||||
Body: string(body), |
||||
} |
||||
|
||||
if err := bus.Dispatch(cmd); err != nil { |
||||
this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name) |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
func TestWebhookNotifier(t *testing.T) { |
||||
Convey("Webhook notifier tests", t, func() { |
||||
|
||||
Convey("Parsing alert notification from settings", func() { |
||||
Convey("empty settings should return error", func() { |
||||
json := `{ }` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "ops", |
||||
Type: "email", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
_, err := NewWebHookNotifier(model) |
||||
So(err, ShouldNotBeNil) |
||||
}) |
||||
|
||||
Convey("from settings", func() { |
||||
json := ` |
||||
{ |
||||
"url": "http://google.com" |
||||
}` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "ops", |
||||
Type: "email", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
not, err := NewWebHookNotifier(model) |
||||
emailNotifier := not.(*WebhookNotifier) |
||||
|
||||
So(err, ShouldBeNil) |
||||
So(emailNotifier.Name, ShouldEqual, "ops") |
||||
So(emailNotifier.Type, ShouldEqual, "email") |
||||
So(emailNotifier.Url, ShouldEqual, "http://google.com") |
||||
}) |
||||
}) |
||||
}) |
||||
} |
Loading…
Reference in new issue