-
Notifications
You must be signed in to change notification settings - Fork 18
/
slack.go
148 lines (127 loc) · 3.16 KB
/
slack.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package clog
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
)
type slackAttachment struct {
Text string `json:"text"`
Color string `json:"color"`
}
type slackPayload struct {
Attachments []slackAttachment `json:"attachments"`
}
var slackColors = []string{
"", // Trace
"#3aa3e3", // Info
"warning", // Warn
"danger", // Error
"#ff0200", // Fatal
}
// SlackConfig is the config object for the Slack logger.
type SlackConfig struct {
// Minimum logging level of messages to be processed.
Level Level
// Slack webhook URL.
URL string
// Colors for different levels, must have exact 5 elements in the order of
// Trace, Info, Warn, Error, and Fatal.
Colors []string
}
var _ Logger = (*slackLogger)(nil)
type slackLogger struct {
*noopLogger
url string
colors []string
client *http.Client
}
func (l *slackLogger) buildPayload(m Messager) (string, error) {
payload := slackPayload{
Attachments: []slackAttachment{
{
Text: m.String(),
Color: l.colors[m.Level()],
},
},
}
p, err := json.Marshal(&payload)
if err != nil {
return "", err
}
return string(p), nil
}
func (l *slackLogger) postMessage(r io.Reader) error {
resp, err := l.client.Post(l.url, "application/json", r)
if err != nil {
return fmt.Errorf("HTTP request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read HTTP response body: %v", err)
}
return fmt.Errorf("non-success response status code %d with body: %s", resp.StatusCode, data)
}
return nil
}
func (l *slackLogger) Write(m Messager) error {
payload, err := l.buildPayload(m)
if err != nil {
return fmt.Errorf("build payload: %v", err)
}
err = l.postMessage(bytes.NewReader([]byte(payload)))
if err != nil {
return fmt.Errorf("post message: %v", err)
}
return nil
}
// DefaultSlackName is the default name for the Slack logger.
const DefaultSlackName = "slack"
// NewSlack initializes and appends a new Slack logger with default name
// to the managed list.
func NewSlack(vs ...interface{}) error {
return NewSlackWithName(DefaultSlackName, vs...)
}
// NewSlackWithName initializes and appends a new Slack logger with given
// name to the managed list.
func NewSlackWithName(name string, vs ...interface{}) error {
return New(name, SlackIniter(), vs...)
}
// SlackIniter returns the initer for the Slack logger.
func SlackIniter() Initer {
return func(name string, vs ...interface{}) (Logger, error) {
var cfg *SlackConfig
for i := range vs {
switch v := vs[i].(type) {
case SlackConfig:
cfg = &v
}
}
if cfg == nil {
return nil, fmt.Errorf("config object with the type '%T' not found", SlackConfig{})
} else if cfg.URL == "" {
return nil, errors.New("empty URL")
}
colors := slackColors
if cfg.Colors != nil {
if len(cfg.Colors) != 5 {
return nil, fmt.Errorf("colors must have exact 5 elements, but got %d", len(cfg.Colors))
}
colors = cfg.Colors
}
return &slackLogger{
noopLogger: &noopLogger{
name: name,
level: cfg.Level,
},
url: cfg.URL,
colors: colors,
client: http.DefaultClient,
}, nil
}
}