Skip to content

Commit 3bbbe7e

Browse files
committed
feat(notification): add
1 parent 61416e2 commit 3bbbe7e

File tree

10 files changed

+243
-30
lines changed

10 files changed

+243
-30
lines changed

agerr/error.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package agerr
2+
3+
import "fmt"
4+
5+
// Wrap wraps an error with `wrapmsg`
6+
func Wrap(wrapmsg string, err error) error {
7+
if err != nil {
8+
return fmt.Errorf(wrapmsg, err)
9+
}
10+
return nil
11+
}

agerr/log.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package agerr
22

3-
import "github.com/agflow/tools/log"
3+
import (
4+
"fmt"
5+
"log"
6+
"time"
7+
)
48

59
// CallAndLog calls function which may return error and logs it.
610
// The intention of this function is to be used with `go` and `defer` clauses.
@@ -11,7 +15,9 @@ func CallAndLog(f func() error) {
1115
// Log logs error unless nil
1216
func Log(err error) {
1317
if err != nil {
14-
log.Errorf("unhandled error %+v", err)
18+
nowStr := time.Now().Format("2006/01/02 15:04:05")
19+
msg := fmt.Sprintf("unhandled error %+v", err)
20+
log.Printf("%s%s %s %s%s", "\033[31m", nowStr, "[ERROR]", msg, "\033[0m")
1521
}
1622
}
1723

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/aws/aws-lambda-go v1.34.1
77
github.com/go-redis/redis/v8 v8.11.5
88
github.com/pkg/errors v0.9.1
9+
github.com/slack-go/slack v0.11.2
910
github.com/stretchr/testify v1.8.0
1011
github.com/thoas/go-funk v0.9.2
1112
)
@@ -14,6 +15,7 @@ require (
1415
github.com/cespare/xxhash/v2 v2.1.2 // indirect
1516
github.com/davecgh/go-spew v1.1.1 // indirect
1617
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
18+
github.com/gorilla/websocket v1.4.2 // indirect
1719
github.com/kr/pretty v0.3.0 // indirect
1820
github.com/pmezard/go-difflib v1.0.0 // indirect
1921
github.com/rogpeppe/go-internal v1.8.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
1111
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
1212
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
1313
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
14+
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
15+
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
16+
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
17+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
18+
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
19+
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1420
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
1521
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
1622
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -30,9 +36,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
3036
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
3137
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
3238
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
39+
github.com/slack-go/slack v0.11.2 h1:IWl90Rk+jqPEVyiBytH27CSN/TFAg2vuDDfoPRog/nc=
40+
github.com/slack-go/slack v0.11.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
3341
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3442
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
3543
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
44+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
3645
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
3746
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
3847
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
@@ -43,6 +52,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacp
4352
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
4453
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
4554
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
55+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4656
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4757
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4858
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

log/log.go

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,84 +16,154 @@ const (
1616
timeFormat = "2006/01/02 15:04:05"
1717
)
1818

19+
// Level type
20+
type Level uint32
21+
22+
const (
23+
// PanicLvl level, highest level of severity. Logs and then calls panic with the
24+
// message passed to Debug, Info, ...
25+
PanicLvl Level = iota
26+
// FatalLvl level. Logs and then calls `logger.Exit(1)`. It will exit even if the
27+
// logging level is set to Panic.
28+
FatalLvl
29+
// ErrorLvl level. Logs. Used for errors that should definitely be noted.
30+
// Commonly used for hooks to send errors to an error tracking service.
31+
ErrorLvl
32+
// WarnLvl level. Non-critical entries that deserve eyes.
33+
WarnLvl
34+
// InfoLvl level. General operational entries about what's going on inside the
35+
// application.
36+
InfoLvl
37+
// DebugLvl level. Usually only enabled when debugging. Very verbose logging.
38+
DebugLvl
39+
)
40+
41+
// Hook is an alias for the hook function
42+
type Hook = func(MetaInfo) error
43+
44+
// Logger defines the logger to be used
45+
type Logger struct {
46+
Hooks []Hook
47+
}
48+
49+
// nolint: gochecknoglobals
50+
var logger Logger
51+
1952
// nolint: gochecknoinits
2053
func init() {
2154
log.SetFlags(0)
55+
logger = Logger{}
56+
}
57+
58+
// MetaInfo is the metadata of the log
59+
type MetaInfo struct {
60+
Msg string
61+
Lvl Level
2262
}
2363

2464
// Info logs in info level
2565
func Info(v ...interface{}) {
26-
nowStr := time.Now().Format(timeFormat)
27-
log.Printf("%s%s %s %s%s", green, nowStr, "[INFO]", fmt.Sprint(v...), reset)
66+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: InfoLvl})
2867
}
2968

3069
// Infof logs in info level with a format
3170
func Infof(format string, v ...interface{}) {
32-
msg := fmt.Sprintf(format, v...)
33-
Info(msg)
71+
Info(fmt.Sprintf(format, v...))
3472
}
3573

3674
// Warn logs in warn level
3775
func Warn(v ...interface{}) {
38-
nowStr := time.Now().Format(timeFormat)
39-
log.Printf("%s%s %s %s%s", yellow, nowStr, "[WARN]", fmt.Sprint(v...), reset)
76+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: WarnLvl})
4077
}
4178

4279
// Warnf logs in warn level with a format
4380
func Warnf(format string, v ...interface{}) {
44-
msg := fmt.Sprintf(format, v...)
45-
Warn(msg)
81+
Warn(fmt.Sprintf(format, v...))
4682
}
4783

4884
// Debug logs in debug level
4985
func Debug(v ...interface{}) {
50-
nowStr := time.Now().Format(timeFormat)
51-
52-
execLine := ""
53-
_, file, line, ok := runtime.Caller(1)
54-
if ok {
55-
execLine = fmt.Sprintf("%s:%d", filepath.Base(file), line)
56-
}
57-
58-
log.Printf("%s%s %s %s %s%s", yellow, nowStr, execLine, "[DEBUG]", fmt.Sprint(v...), reset)
86+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: DebugLvl})
5987
}
6088

6189
// Debugf logs in debug level with a format
6290
func Debugf(format string, v ...interface{}) {
63-
msg := fmt.Sprintf(format, v...)
64-
Debug(msg)
91+
Debug(fmt.Sprintf(format, v...))
6592
}
6693

67-
// IfErrorDiffNil logs Error if argument is not nil
94+
// IfErrorDiffNil logs Error if argument is not nil. DEPRECATED
6895
func IfErrorDiffNil(v interface{}) {
6996
if v != nil {
7097
Error(v)
7198
}
7299
}
73100

101+
// ErrorType logs the error if the argument is not nil
102+
func ErrorType(err error) {
103+
if err != nil {
104+
Error(err)
105+
}
106+
}
107+
74108
// Error logs in error level
75109
func Error(v ...interface{}) {
76-
nowStr := time.Now().Format(timeFormat)
77-
log.Printf("%s%s %s %s%s", red, nowStr, "[ERROR]", fmt.Sprint(v...), reset)
110+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: ErrorLvl})
78111
}
79112

80113
// Errorf logs in error level with a format
81114
func Errorf(format string, v ...interface{}) {
82-
msg := fmt.Sprintf(format, v...)
83-
Error(msg)
115+
Error(fmt.Sprintf(format, v...))
84116
}
85117

86118
// Fatalf logs in error level with a format
87119
func Fatalf(format string, v ...interface{}) {
88-
log.Fatalf(format, v...)
120+
Fatal(fmt.Sprintf(format, v...))
89121
}
90122

91123
// Fatal logs in error level
92124
func Fatal(v ...interface{}) {
93-
log.Fatal(v...)
125+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: FatalLvl})
94126
}
95127

96128
// Panic logs in error level with a posterior Panic()
97129
func Panic(v ...interface{}) {
98-
log.Panic(v...)
130+
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: PanicLvl})
131+
}
132+
133+
// Panicf logs in error level with a posterior Panic() with format
134+
func Panicf(format string, v ...interface{}) {
135+
Panic(fmt.Sprintf(format, v...))
136+
}
137+
138+
func (li *MetaInfo) log() {
139+
nowStr := time.Now().Format(timeFormat)
140+
switch li.Lvl {
141+
case PanicLvl:
142+
log.Panic(li.Msg)
143+
case FatalLvl:
144+
log.Fatal(li.Msg)
145+
case ErrorLvl:
146+
log.Printf("%s%s %s %s%s", red, nowStr, "[ERROR]", li.Msg, reset)
147+
case WarnLvl:
148+
log.Printf("%s%s %s %s%s", yellow, nowStr, "[WARN]", li.Msg, reset)
149+
case InfoLvl:
150+
log.Printf("%s%s %s %s%s", green, nowStr, "[INFO]", li.Msg, reset)
151+
case DebugLvl:
152+
execLine := ""
153+
_, file, line, ok := runtime.Caller(1)
154+
if ok {
155+
execLine = fmt.Sprintf("%s:%d", filepath.Base(file), line)
156+
}
157+
log.Printf("%s%s %s %s %s%s", yellow, nowStr, execLine, "[DEBUG]", li.Msg, reset)
158+
}
159+
}
160+
161+
func exec(li MetaInfo) {
162+
li.log()
163+
for _, hook := range logger.Hooks {
164+
nowStr := time.Now().Format(timeFormat)
165+
if err := hook(li); err != nil {
166+
log.Printf("%s%s %s %v%s", red, nowStr, "[ERROR]", err, reset)
167+
}
168+
}
99169
}

log/slack.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package log
2+
3+
import "github.com/agflow/tools/notification/slack"
4+
5+
// NewSlackHook returns a hook for slack
6+
func NewSlackHook(token string) Hook {
7+
return func(info MetaInfo) error {
8+
var color string
9+
switch info.Lvl {
10+
case InfoLvl:
11+
color = slack.ColorGood
12+
case FatalLvl, PanicLvl, ErrorLvl:
13+
color = slack.ColorDanger
14+
default:
15+
color = slack.ColorWarning
16+
}
17+
msg := info.Msg + "\n"
18+
slackCli := slack.New(token, true)
19+
return slackCli.SendWithColor("feed-worker", msg, color)
20+
}
21+
}

notification/service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package notification
2+
3+
// Service is an interface of notification.Service
4+
type Service interface {
5+
Send(string, string) error
6+
}

notification/slack/slack.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package slack
2+
3+
import (
4+
"github.com/slack-go/slack"
5+
6+
"github.com/agflow/tools/agerr"
7+
)
8+
9+
const (
10+
defaultChannel = "test-notifications"
11+
// ColorGood is slack's color "good"
12+
ColorGood = "good"
13+
// ColorWarning is slack's color "warning"
14+
ColorWarning = "warning"
15+
// ColorDanger is slack's color "danger"
16+
ColorDanger = "danger"
17+
)
18+
19+
// Client is wrapper of a slack.Client
20+
type Client struct {
21+
slackCli *slack.Client
22+
enabled bool
23+
}
24+
25+
// New return a new notifications/slack.Client
26+
func New(token string, enabled bool) *Client {
27+
return &Client{slackCli: slack.New(token), enabled: enabled}
28+
}
29+
30+
func getChannel(channel string) string {
31+
if channel == "" {
32+
return defaultChannel
33+
}
34+
return channel
35+
}
36+
37+
// Send sends a notification message to slack
38+
func (c *Client) Send(channel, msg string) error {
39+
if !c.enabled {
40+
return nil
41+
}
42+
43+
channel = getChannel(channel)
44+
45+
_, _, err := c.slackCli.PostMessage(
46+
channel,
47+
slack.MsgOptionAsUser(true),
48+
slack.MsgOptionText(msg, false))
49+
return agerr.Wrap("can't send slack notification: %w", err)
50+
}
51+
52+
// SendWithColor sends a notification message to slack as an attachment with color
53+
func (c *Client) SendWithColor(channel, msg, color string) error {
54+
if !c.enabled {
55+
return nil
56+
}
57+
58+
channel = getChannel(channel)
59+
attachment := slack.Attachment{
60+
Text: msg,
61+
Color: color,
62+
}
63+
64+
_, _, err := c.slackCli.PostMessage(channel,
65+
slack.MsgOptionAttachments(attachment),
66+
slack.MsgOptionAsUser(true),
67+
)
68+
return agerr.Wrap("can't send slack notification with color: %w", err)
69+
}

notification/slack/slack_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package slack
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestSend(t *testing.T) {
10+
slackCli := New("xoxb-2314993037-3480243399810-eKklHxCNGvH7E3dnGaknRpZL", true)
11+
require.Nil(t, slackCli.Send(defaultChannel, "this is a test notification"))
12+
require.Nil(t, slackCli.SendWithColor(
13+
defaultChannel,
14+
"this is a test notification with a color",
15+
ColorGood,
16+
),
17+
)
18+
}

sql/db/service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Client struct {
1111
DB *sql.DB
1212
}
1313

14-
// Service is an interface od db.Service
14+
// Service is an interface of db.Service
1515
type Service interface {
1616
Select(interface{}, string, ...interface{}) error
1717
Close() error

0 commit comments

Comments
 (0)