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
This commit is contained in:
Julius Volz 2013-08-20 15:42:06 +02:00
parent d74c2c54d4
commit 1eb1ceac8c
5 changed files with 58 additions and 27 deletions

11
main.go
View file

@ -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{}

View file

@ -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,
},
})

View file

@ -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",
},
}

View file

@ -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))
}

View file

@ -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