-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
discordrus.go
189 lines (163 loc) · 5.09 KB
/
discordrus.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package discordrus
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/sirupsen/logrus"
"time"
)
const (
maxFieldNum = 25
minUsernameChars = 2
maxUsernameChars = 32
maxAuthorChars = 256
maxFieldNameChars = 256
maxFieldValueChars = 1024
maxDescriptionChars = 2048
usernameTooShortMsg = " (USERNAME TOO SHORT)"
)
// Opts contains the options available for the hook
type Opts struct {
// Username replaces the default username of the webhook bot for the sent message only if set (default: none)
Username string
// Author adds an author field if set (default: none)
Author string
// DisableInlineFields causes fields to be displayed one per line as opposed to being inline (i.e., in columns) (default: false)
DisableInlineFields bool
// EnableCustomColors specifies whether CustomLevelColors should be used instead of DefaultLevelColors (default: true)
EnableCustomColors bool
// CustomLevelColors is a LevelColors struct which replaces DefaultLevelColors if EnableCustomColors is set to true (default: none)
CustomLevelColors *LevelColors
// DisableTimestamp specifies whether the timestamp in the footer should be disabled (default: false)
DisableTimestamp bool
// TimestampFormat specifies a custom format for the footer
TimestampFormat string
// TimestampLocale specifies a custom locale for the timestamp
TimestampLocale *time.Location
}
// Hook is a hook to send logs to Discord
type Hook struct {
// WebhookURL is the full Discord webhook URL
WebhookURL string
// MinLevel is the minimum priority level to enable logging for
MinLevel logrus.Level
// Opts contains the options available for the hook
Opts *Opts
}
// NewHook creates a new instance of a hook, ensures correct string lengths and returns its pointer
func NewHook(webhookURL string, minLevel logrus.Level, opts *Opts) *Hook {
hook := Hook{
WebhookURL: webhookURL,
MinLevel: minLevel,
Opts: opts,
}
// Ensure correct username length
if hook.Opts.Username != "" && len(hook.Opts.Username) < minUsernameChars {
// Append "(USERNAME TOO SHORT)" in order not to disrupt logging operations
hook.Opts.Username = hook.Opts.Username + usernameTooShortMsg
} else if len(hook.Opts.Username) > maxUsernameChars {
hook.Opts.Username = hook.Opts.Username[:maxUsernameChars]
}
// Truncate author
if len(hook.Opts.Author) > maxAuthorChars {
hook.Opts.Author = hook.Opts.Author[:maxAuthorChars]
}
return &hook
}
func (hook *Hook) Fire(entry *logrus.Entry) error {
// Parse the entry to a Discord webhook object in JSON form
webhookObject, err := hook.parseToJson(entry)
if err != nil {
return err
}
err = hook.send(webhookObject)
if err != nil {
return err
}
return nil
}
func (hook *Hook) Levels() []logrus.Level {
return LevelThreshold(hook.MinLevel)
}
func (hook *Hook) parseToJson(entry *logrus.Entry) (*[]byte, error) {
// Create struct mapping to Discord webhook object
var data = map[string]interface{}{
"embeds": []map[string]interface{}{},
}
var embed = map[string]interface{}{
"title": strings.ToUpper(entry.Level.String()),
}
var fields = []map[string]interface{}{}
// Add username to data
if hook.Opts.Username != "" {
data["username"] = hook.Opts.Username
}
// Add description to embed
if len(entry.Message) > maxDescriptionChars {
entry.Message = entry.Message[:maxDescriptionChars]
}
embed["description"] = entry.Message
// Add color to embed
if hook.Opts.EnableCustomColors {
embed["color"] = hook.Opts.CustomLevelColors.LevelColor(entry.Level)
} else {
embed["color"] = DefaultLevelColors.LevelColor(entry.Level)
}
// Add author to embed
if hook.Opts.Author != "" {
embed["author"] = map[string]interface{}{"name": hook.Opts.Author}
}
// Add footer to embed
if !hook.Opts.DisableTimestamp {
if hook.Opts.TimestampLocale != nil {
entry.Time = entry.Time.In(hook.Opts.TimestampLocale)
}
timestamp := ""
if hook.Opts.TimestampFormat != "" {
timestamp = entry.Time.Format(hook.Opts.TimestampFormat)
} else {
timestamp = entry.Time.String()
}
embed["footer"] = map[string]interface{}{
"text": timestamp,
}
}
// Add fields to embed
counter := 0
for name, value := range entry.Data {
// Ensure that the maximum field number is not exceeded
if counter > maxFieldNum {
break
}
// Make value a string
valueStr := fmt.Sprintf("%v", value)
// Truncate names and values which are too long
if len(name) > maxFieldNameChars {
name = name[:maxFieldNameChars]
}
if len(valueStr) > maxFieldValueChars {
valueStr = valueStr[:maxFieldValueChars]
}
var embedField = map[string]interface{}{
"name": name,
"value": valueStr,
"inline": !hook.Opts.DisableInlineFields,
}
fields = append(fields, embedField)
counter++
}
// Merge fields and embed into data
embed["fields"] = fields
data["embeds"] = []map[string]interface{}{embed}
marshaled, err := json.Marshal(data)
return &marshaled, err
}
func (hook *Hook) send(webhookObject *[]byte) error {
_, err := http.Post(hook.WebhookURL, "application/json; charset=utf-8", bytes.NewBuffer(*webhookObject))
if err != nil {
return err
}
return nil
}