From 1eb1ceac8cb9391e23b1cbc084faea5265029566 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Tue, 20 Aug 2013 15:42:06 +0200 Subject: [PATCH] Add alert-expression console links to notifications. The ConsoleLinkForExpression() function now escapes console URLs in such a way that works both in emails and in HTML. Change-Id: I917bae0b526cbbac28ccd2a4ec3c5ac03ee4c647 --- main.go | 11 +++++--- notification/notification.go | 9 +++---- notification/notification_test.go | 9 ++++--- rules/helpers.go | 13 ++++++++-- rules/manager.go | 43 +++++++++++++++++++++---------- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 06e8c6362..876b5c107 100644 --- a/main.go +++ b/main.go @@ -203,14 +203,19 @@ func main() { notifications := make(chan notification.NotificationReqs, *notificationQueueCapacity) // Queue depth will need to be exposed - ruleManager := rules.NewRuleManager(unwrittenSamples, notifications, conf.EvaluationInterval(), ts) + ruleManager := rules.NewRuleManager(&rules.RuleManagerOptions{ + Results: unwrittenSamples, + Notifications: notifications, + EvaluationInterval: conf.EvaluationInterval(), + Storage: ts, + PrometheusUrl: web.MustBuildServerUrl(), + }) if err := ruleManager.AddRulesFromConfig(conf); err != nil { glog.Fatal("Error loading rule files: ", err) } go ruleManager.Run() - prometheusUrl := web.MustBuildServerUrl() - notificationHandler := notification.NewNotificationHandler(*alertmanagerUrl, prometheusUrl, notifications) + notificationHandler := notification.NewNotificationHandler(*alertmanagerUrl, notifications) go notificationHandler.Run() flags := map[string]string{} diff --git a/notification/notification.go b/notification/notification.go index 0d6a99109..7d09a501d 100644 --- a/notification/notification.go +++ b/notification/notification.go @@ -54,6 +54,8 @@ type NotificationReq struct { ActiveSince time.Time // A textual representation of the rule that triggered the alert. RuleString string + // Prometheus console link to alert expression. + GeneratorUrl string } type NotificationReqs []*NotificationReq @@ -67,8 +69,6 @@ type httpPoster interface { type NotificationHandler struct { // The URL of the alert manager to send notifications to. alertmanagerUrl string - // The URL of this Prometheus instance to include in notifications. - prometheusUrl string // Buffer of notifications that have not yet been sent. pendingNotifications <-chan NotificationReqs // HTTP client with custom timeout settings. @@ -76,12 +76,11 @@ type NotificationHandler struct { } // Construct a new NotificationHandler. -func NewNotificationHandler(alertmanagerUrl string, prometheusUrl string, notificationReqs <-chan NotificationReqs) *NotificationHandler { +func NewNotificationHandler(alertmanagerUrl string, notificationReqs <-chan NotificationReqs) *NotificationHandler { return &NotificationHandler{ alertmanagerUrl: alertmanagerUrl, pendingNotifications: notificationReqs, httpClient: utility.NewDeadlineClient(*deadline), - prometheusUrl: prometheusUrl, } } @@ -132,7 +131,7 @@ func (n *NotificationHandler) sendNotifications(reqs NotificationReqs) error { "Payload": map[string]interface{}{ "Value": req.Value, "ActiveSince": req.ActiveSince, - "GeneratorUrl": n.prometheusUrl, + "GeneratorUrl": req.GeneratorUrl, "AlertingRule": req.RuleString, }, }) diff --git a/notification/notification_test.go b/notification/notification_test.go index 0d707d827..e01e6c42b 100644 --- a/notification/notification_test.go +++ b/notification/notification_test.go @@ -48,7 +48,7 @@ type testNotificationScenario struct { func (s *testNotificationScenario) test(i int, t *testing.T) { notifications := make(chan NotificationReqs) defer close(notifications) - h := NewNotificationHandler("alertmanager_url", "prometheus_url", notifications) + h := NewNotificationHandler("alertmanager_url", notifications) receivedPost := make(chan bool, 1) poster := testHttpPoster{receivedPost: receivedPost} @@ -63,9 +63,10 @@ func (s *testNotificationScenario) test(i int, t *testing.T) { Labels: clientmodel.LabelSet{ clientmodel.LabelName("instance"): clientmodel.LabelValue("testinstance"), }, - Value: clientmodel.SampleValue(1.0 / 3.0), - ActiveSince: time.Time{}, - RuleString: "Test rule string", + Value: clientmodel.SampleValue(1.0 / 3.0), + ActiveSince: time.Time{}, + RuleString: "Test rule string", + GeneratorUrl: "prometheus_url", }, } diff --git a/rules/helpers.go b/rules/helpers.go index 511f6d811..86f3e53ea 100644 --- a/rules/helpers.go +++ b/rules/helpers.go @@ -15,7 +15,8 @@ package rules import ( "fmt" - "html" + "net/url" + "strings" clientmodel "github.com/prometheus/client_golang/model" @@ -116,5 +117,13 @@ func NewMatrix(vector ast.Node, intervalStr string) (ast.MatrixNode, error) { } func ConsoleLinkForExpression(expr string) string { - return html.EscapeString(fmt.Sprintf(`graph#[{"expr":%q,"tab":1}]`, expr)) + // url.QueryEscape percent-escapes everything except spaces, for which it + // uses "+". However, in the non-query part of a URI, only percent-escaped + // spaces are legal, so we need to manually replace "+" with "%20" after + // query-escaping the string. + // + // See also: + // http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20. + urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr)) + return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1)) } diff --git a/rules/manager.go b/rules/manager.go index 87312d829..aa1d737f3 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -45,21 +45,37 @@ type ruleManager struct { sync.Mutex rules []Rule + done chan bool + + interval time.Duration + storage *metric.TieredStorage + results chan<- *extraction.Result notifications chan<- notification.NotificationReqs - done chan bool - interval time.Duration - storage *metric.TieredStorage + + prometheusUrl string } -func NewRuleManager(results chan<- *extraction.Result, notifications chan<- notification.NotificationReqs, interval time.Duration, storage *metric.TieredStorage) RuleManager { +type RuleManagerOptions struct { + EvaluationInterval time.Duration + Storage *metric.TieredStorage + + Notifications chan<- notification.NotificationReqs + Results chan<- *extraction.Result + + PrometheusUrl string +} + +func NewRuleManager(o *RuleManagerOptions) RuleManager { manager := &ruleManager{ - results: results, - notifications: notifications, - rules: []Rule{}, - done: make(chan bool), - interval: interval, - storage: storage, + rules: []Rule{}, + done: make(chan bool), + + interval: o.EvaluationInterval, + storage: o.Storage, + results: o.Results, + notifications: o.Notifications, + prometheusUrl: o.PrometheusUrl, } return manager } @@ -107,9 +123,10 @@ func (m *ruleManager) queueAlertNotifications(rule *AlertingRule) { Labels: aa.Labels.Merge(clientmodel.LabelSet{ AlertNameLabel: clientmodel.LabelValue(rule.Name()), }), - Value: aa.Value, - ActiveSince: aa.ActiveSince, - RuleString: rule.String(), + Value: aa.Value, + ActiveSince: aa.ActiveSince, + RuleString: rule.String(), + GeneratorUrl: m.prometheusUrl + ConsoleLinkForExpression(rule.vector.String()), }) } m.notifications <- notifications