mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
Alerting: Update Discord receiver to use encoding/json to build a webhook message + truncate long message (#60592)
* replace simplejson with models * truncate too long messages Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>
This commit is contained in:
parent
aaa55b4252
commit
4a3097f52a
@ -13,13 +13,53 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/alerting/alerting/notifier/channels"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
)
|
||||
|
||||
// Constants and models are set according to the official documentation https://discord.com/developers/docs/resources/webhook#execute-webhook-jsonform-params
|
||||
|
||||
type discordEmbedType string
|
||||
|
||||
const (
|
||||
discordRichEmbed discordEmbedType = "rich"
|
||||
|
||||
discordMaxEmbeds = 10
|
||||
discordMaxMessageLen = 2000
|
||||
)
|
||||
|
||||
type discordMessage struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Content string `json:"content"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
Embeds []discordLinkEmbed `json:"embeds,omitempty"`
|
||||
}
|
||||
|
||||
// discordLinkEmbed implements https://discord.com/developers/docs/resources/channel#embed-object
|
||||
type discordLinkEmbed struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Type discordEmbedType `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Color int64 `json:"color,omitempty"`
|
||||
|
||||
Footer *discordFooter `json:"footer,omitempty"`
|
||||
|
||||
Image *discordImage `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
// discordFooter implements https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
|
||||
type discordFooter struct {
|
||||
Text string `json:"text"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
}
|
||||
|
||||
// discordImage implements https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
|
||||
type discordImage struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type DiscordNotifier struct {
|
||||
*channels.Base
|
||||
log channels.Logger
|
||||
@ -64,8 +104,6 @@ type discordAttachment struct {
|
||||
state model.AlertStatus
|
||||
}
|
||||
|
||||
const DiscordMaxEmbeds = 10
|
||||
|
||||
func DiscordFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {
|
||||
dn, err := newDiscordNotifier(fc)
|
||||
if err != nil {
|
||||
@ -96,69 +134,78 @@ func newDiscordNotifier(fc channels.FactoryConfig) (*DiscordNotifier, error) {
|
||||
func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
alerts := types.Alerts(as...)
|
||||
|
||||
bodyJSON := simplejson.New()
|
||||
var msg discordMessage
|
||||
|
||||
if !d.settings.UseDiscordUsername {
|
||||
bodyJSON.Set("username", "Grafana")
|
||||
msg.Username = "Grafana"
|
||||
}
|
||||
|
||||
var tmplErr error
|
||||
tmpl, _ := channels.TmplText(ctx, d.tmpl, as, d.log, &tmplErr)
|
||||
|
||||
bodyJSON.Set("content", tmpl(d.settings.Message))
|
||||
msg.Content = tmpl(d.settings.Message)
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord notification content", "error", tmplErr.Error())
|
||||
// Reset tmplErr for templating other fields.
|
||||
tmplErr = nil
|
||||
}
|
||||
truncatedMsg, truncated := channels.TruncateInRunes(msg.Content, discordMaxMessageLen)
|
||||
if truncated {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
d.log.Warn("Truncated content", "key", key, "max_runes", discordMaxMessageLen)
|
||||
msg.Content = truncatedMsg
|
||||
}
|
||||
|
||||
if d.settings.AvatarURL != "" {
|
||||
bodyJSON.Set("avatar_url", tmpl(d.settings.AvatarURL))
|
||||
msg.AvatarURL = tmpl(d.settings.AvatarURL)
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord Avatar URL", "error", tmplErr.Error(), "fallback", d.settings.AvatarURL)
|
||||
bodyJSON.Set("avatar_url", d.settings.AvatarURL)
|
||||
msg.AvatarURL = d.settings.AvatarURL
|
||||
tmplErr = nil
|
||||
}
|
||||
}
|
||||
|
||||
footer := map[string]interface{}{
|
||||
"text": "Grafana v" + d.appVersion,
|
||||
"icon_url": "https://grafana.com/static/assets/img/fav32.png",
|
||||
footer := &discordFooter{
|
||||
Text: "Grafana v" + d.appVersion,
|
||||
IconURL: "https://grafana.com/static/assets/img/fav32.png",
|
||||
}
|
||||
|
||||
linkEmbed := simplejson.New()
|
||||
var linkEmbed discordLinkEmbed
|
||||
|
||||
linkEmbed.Set("title", tmpl(d.settings.Title))
|
||||
linkEmbed.Title = tmpl(d.settings.Title)
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord notification title", "error", tmplErr.Error())
|
||||
// Reset tmplErr for templating other fields.
|
||||
tmplErr = nil
|
||||
}
|
||||
linkEmbed.Set("footer", footer)
|
||||
linkEmbed.Set("type", "rich")
|
||||
linkEmbed.Footer = footer
|
||||
linkEmbed.Type = discordRichEmbed
|
||||
|
||||
color, _ := strconv.ParseInt(strings.TrimLeft(getAlertStatusColor(alerts.Status()), "#"), 16, 0)
|
||||
linkEmbed.Set("color", color)
|
||||
linkEmbed.Color = color
|
||||
|
||||
ruleURL := joinUrlPath(d.tmpl.ExternalURL.String(), "/alerting/list", d.log)
|
||||
linkEmbed.Set("url", ruleURL)
|
||||
linkEmbed.URL = ruleURL
|
||||
|
||||
embeds := []interface{}{linkEmbed}
|
||||
embeds := []discordLinkEmbed{linkEmbed}
|
||||
|
||||
attachments := d.constructAttachments(ctx, as, DiscordMaxEmbeds-1)
|
||||
attachments := d.constructAttachments(ctx, as, discordMaxEmbeds-1)
|
||||
for _, a := range attachments {
|
||||
color, _ := strconv.ParseInt(strings.TrimLeft(getAlertStatusColor(alerts.Status()), "#"), 16, 0)
|
||||
embed := map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"url": a.url,
|
||||
embed := discordLinkEmbed{
|
||||
Image: &discordImage{
|
||||
URL: a.url,
|
||||
},
|
||||
"color": color,
|
||||
"title": a.alertName,
|
||||
Color: color,
|
||||
Title: a.alertName,
|
||||
}
|
||||
embeds = append(embeds, embed)
|
||||
}
|
||||
|
||||
bodyJSON.Set("embeds", embeds)
|
||||
msg.Embeds = embeds
|
||||
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord message", "error", tmplErr.Error())
|
||||
@ -171,7 +218,7 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
u = d.settings.WebhookURL
|
||||
}
|
||||
|
||||
body, err := json.Marshal(bodyJSON)
|
||||
body, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/alerting/alerting/notifier/channels"
|
||||
@ -283,6 +284,36 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Should truncate too long messages",
|
||||
settings: fmt.Sprintf(`{
|
||||
"url": "http://localhost",
|
||||
"use_discord_username": true,
|
||||
"message": "%s"
|
||||
}`, strings.Repeat("Y", discordMaxMessageLen+rand.Intn(100)+1)),
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"content": strings.Repeat("Y", discordMaxMessageLen-1) + "…",
|
||||
"embeds": []interface{}{map[string]interface{}{
|
||||
"color": 1.4037554e+07,
|
||||
"footer": map[string]interface{}{
|
||||
"icon_url": "https://grafana.com/static/assets/img/fav32.png",
|
||||
"text": "Grafana v" + appVersion,
|
||||
},
|
||||
"title": "[FIRING:1] (val1)",
|
||||
"url": "http://localhost/alerting/list",
|
||||
"type": "rich",
|
||||
}},
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
Loading…
Reference in New Issue
Block a user