Outlined slack notification support

This commit is contained in:
Fabian Reinartz 2015-10-09 10:48:25 +02:00
parent c8e0343660
commit e209c8b4fc
3 changed files with 118 additions and 6 deletions

View File

@ -27,6 +27,14 @@ var (
DefaultSlackConfig = SlackConfig{
ColorFiring: "warning",
ColorResolved: "good",
Templates: SlackTemplates{
Title: "slack_default_title",
TitleLink: "slack_default_title_link",
Pretext: "slack_default_pretext",
Text: "slack_default_text",
Fallback: "slack_default_fallback",
},
}
)
@ -151,8 +159,7 @@ func (c *HipchatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Configuration for notification via Slack.
type SlackConfig struct {
// Slack webhook URL, (https://api.slack.com/incoming-webhooks).
WebhookURL string `yaml:"webhook_url"`
URL string `yaml:"url"`
// Slack channel override, (like #other-channel or @username).
Channel string `yaml:"channel"`
@ -161,10 +168,20 @@ type SlackConfig struct {
ColorFiring string `yaml:"color_firing"`
ColorResolved string `yaml:"color_resolved"`
Templates SlackTemplates `yaml:"templates"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
type SlackTemplates struct {
Title string `yaml:"title"`
TitleLink string `yaml:"title_link"`
Pretext string `yaml:"pretext"`
Text string `yaml:"text"`
Fallback string `yaml:"fallback"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSlackConfig
@ -172,8 +189,8 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.WebhookURL == "" {
return fmt.Errorf("missing webhook URL in Slack config")
if c.URL == "" {
return fmt.Errorf("missing URL in Slack config")
}
if c.Channel == "" {
return fmt.Errorf("missing channel in Slack config")

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"text/template"
"github.com/prometheus/common/log"
"github.com/prometheus/common/model"
@ -67,7 +68,6 @@ func (w *Webhook) Notify(ctx context.Context, alerts ...*types.Alert) error {
return err
}
// TODO(fabxc): implement retrying as long as context is not canceled.
resp, err := ctxhttp.Post(ctx, http.DefaultClient, w.URL, contentTypeJSON, &buf)
if err != nil {
return err
@ -80,3 +80,98 @@ func (w *Webhook) Notify(ctx context.Context, alerts ...*types.Alert) error {
return nil
}
type Slack struct {
conf *config.SlackConfig
}
// slackReq is the request for sending a slack notification.
type slackReq struct {
Channel string `json:"channel,omitempty"`
Attachments []slackAttachment `json:"attachments"`
}
// slackAttachment is used to display a richly-formatted message block.
type slackAttachment struct {
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Pretext string `json:"pretext,omitempty"`
Text string `json:"text"`
Fallback string `json:"fallback"`
Color string `json:"color,omitempty"`
MrkdwnIn []string `json:"mrkdwn_in,omitempty"`
Fields []slackAttachmentField `json:"fields,omitempty"`
}
// slackAttachmentField is displayed in a table inside the message attachment.
type slackAttachmentField struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short,omitempty"`
}
func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error {
var (
alerts = types.Alerts(as...)
color = n.conf.ColorResolved
status = string(model.AlertResolved)
)
if alerts.HasFiring() {
color = n.conf.ColorFiring
status = string(model.AlertFiring)
}
var title, link, pretext, text, fallback bytes.Buffer
if err := tmpl.ExecuteTemplate(&title, n.conf.Templates.Title, alerts); err != nil {
return err
}
if err := tmpl.ExecuteTemplate(&text, n.conf.Templates.Text, alerts); err != nil {
return err
}
attachment := &slackAttachment{
Title: title.String(),
TitleLink: link.String(),
Pretext: pretext.String(),
Text: text.String(),
Fallback: fallback.String(),
Fields: []slackAttachmentField{{
Title: "Status",
Value: status,
Short: true,
}},
Color: color,
MrkdwnIn: []string{"fallback", "pretext"},
}
req := &slackReq{
Channel: n.conf.Channel,
Attachments: []slackAttachment{*attachment},
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(req); err != nil {
return err
}
resp, err := ctxhttp.Post(ctx, http.DefaultClient, n.conf.URL, contentTypeJSON, &buf)
if err != nil {
return err
}
// TODO(fabxc): is 2xx status code really indicator for success for Slack API?
resp.Body.Close()
if resp.StatusCode/100 != 2 {
return fmt.Errorf("unexpected status code %v", resp.StatusCode)
}
return nil
}
var tmpl *template.Template
func init() {
tmpl = template.Must(template.ParseGlob("templates/*.tmpl"))
}

View File

@ -119,7 +119,7 @@ func (n *RetryNotifier) Notify(ctx context.Context, alerts ...*types.Alert) erro
select {
case <-tick.C:
if err := n.Notifier.Notify(ctx, alerts...); err != nil {
log.Warnf("notify attempt %d failed: %s", i, err)
log.Warnf("Notify attempt %d failed: %s", i, err)
} else {
return nil
}