A slack notification is useful when
- A long-running job is started
- When some critical error
- A specific request comes through the process as cache invalidate, critical delete request, etc...
However slack provides simple webhooks to notify a channel, it has more features like action, workflow, poll, etc... So the slack go libraries need to comply with these features and that makes the library heavy by bringing internal dependencies. We can write a small tool in Go just for webhooks. It just uses standard libraries.
Usage
Create a slack client with an optional user name one per process. The SendSlackNotification() can send a simple notification to the given channel.
Options: Username - the name of the bot in a slack message
Timeout - default would be 5 seconds
Icon_emoji - the name of the icon image, otherwise it is a default image of incoming webhook.
Sample
sc := SlackClient{
WebHookUrl: "https://WEB_HOOK_URL",
UserName: "USER_NAME",
Channel: "CHANNEL_NAME",
}
sr := SimpleSlackRequest{
Text: "This is test message",
IconEmoji: ":ghost:",
}
err := sc.SendSlackNotification(sr)
if err != nil {
log.Fatal(err)
}
To send a notification with status (slack attachments)
sr := SlackJobNotification{
Text: "This is attachment message",
Details: "details of the jobs",
Color: "warning",
IconEmoji: ":hammer_and_wrench",
}
err := sc.SendJobNotification(sr)
if err != nil {
log.Fatal(err)
}
Source
package notification
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"strconv"
"time"
)
const DefaultSlackTimeout = 5 * time.Second
type SlackClient struct {
WebHookUrl string
UserName string
Channel string
TimeOut time.Duration
}
type SimpleSlackRequest struct {
Text string
IconEmoji string
}
type SlackJobNotification struct {
Color string
IconEmoji string
Details string
Text string
}
type SlackMessage struct {
Username string `json:"username,omitempty"`
IconEmoji string `json:"icon_emoji,omitempty"`
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
}
type Attachment struct {
Color string `json:"color,omitempty"`
Fallback string `json:"fallback,omitempty"`
CallbackID string `json:"callback_id,omitempty"`
ID int `json:"id,omitempty"`
AuthorID string `json:"author_id,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorSubname string `json:"author_subname,omitempty"`
AuthorLink string `json:"author_link,omitempty"`
AuthorIcon string `json:"author_icon,omitempty"`
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Pretext string `json:"pretext,omitempty"`
Text string `json:"text,omitempty"`
ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
// Fields and actions are not defined.
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
Ts json.Number `json:"ts,omitempty"`
}
// SendSlackNotification will post to an 'Incoming Webook' url setup in Slack Apps. It accepts
// some text and the slack channel is saved within Slack.
func (sc SlackClient) SendSlackNotification(sr SimpleSlackRequest) error {
slackRequest := SlackMessage{
Text: sr.Text,
Username: sc.UserName,
IconEmoji: sr.IconEmoji,
Channel: sc.Channel,
}
return sc.sendHttpRequest(slackRequest)
}
func (sc SlackClient) SendJobNotification(job SlackJobNotification) error {
attachment := Attachment{
Color: job.Color,
Text: job.Details,
Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
}
slackRequest := SlackMessage{
Text: job.Text,
Username: sc.UserName,
IconEmoji: job.IconEmoji,
Channel: sc.Channel,
Attachments: []Attachment{attachment},
}
return sc.sendHttpRequest(slackRequest)
}
func (sc SlackClient) SendError(message string, options ...string) (err error) {
return sc.funcName("danger", message, options)
}
func (sc SlackClient) SendInfo(message string, options ...string) (err error) {
return sc.funcName("good", message, options)
}
func (sc SlackClient) SendWarning(message string, options ...string) (err error) {
return sc.funcName("warning", message, options)
}
func (sc SlackClient) funcName(color string, message string, options []string) error {
emoji := ":hammer_and_wrench"
if len(options) > 0 {
emoji = options[0]
}
sjn := SlackJobNotification{
Color: color,
IconEmoji: emoji,
Details: message,
}
return sc.SendJobNotification(sjn)
}
func (sc SlackClient) sendHttpRequest(slackRequest SlackMessage) error {
slackBody, _ := json.Marshal(slackRequest)
req, err := http.NewRequest(http.MethodPost, sc.WebHookUrl, bytes.NewBuffer(slackBody))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
if sc.TimeOut == 0 {
sc.TimeOut = DefaultSlackTimeout
}
client := &http.Client{Timeout: sc.TimeOut}
resp, err := client.Do(req)
if err != nil {
return err
}
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return err
}
if buf.String() != "ok" {
return errors.New("Non-ok response returned from Slack")
}
return nil
}
Top comments (1)
Hi there, great post, many thanks. I’ve been dealing with something very similar at the moment and find this post very helpful.
Pls, don’t you mind adding here main() function with test data that could be used in the Slack notification?
Many thanks.