Skip to content

Commit

Permalink
feat(notification): add
Browse files Browse the repository at this point in the history
  • Loading branch information
ArielHAlba committed Aug 18, 2022
1 parent 61416e2 commit 3bbbe7e
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 30 deletions.
11 changes: 11 additions & 0 deletions agerr/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package agerr

import "fmt"

// Wrap wraps an error with `wrapmsg`
func Wrap(wrapmsg string, err error) error {
if err != nil {
return fmt.Errorf(wrapmsg, err)
}
return nil
}
10 changes: 8 additions & 2 deletions agerr/log.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package agerr

import "github.com/agflow/tools/log"
import (
"fmt"
"log"
"time"
)

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

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/aws/aws-lambda-go v1.34.1
github.com/go-redis/redis/v8 v8.11.5
github.com/pkg/errors v0.9.1
github.com/slack-go/slack v0.11.2
github.com/stretchr/testify v1.8.0
github.com/thoas/go-funk v0.9.2
)
Expand All @@ -14,6 +15,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
Expand All @@ -30,9 +36,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/slack-go/slack v0.11.2 h1:IWl90Rk+jqPEVyiBytH27CSN/TFAg2vuDDfoPRog/nc=
github.com/slack-go/slack v0.11.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
Expand All @@ -43,6 +52,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacp
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
124 changes: 97 additions & 27 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,84 +16,154 @@ const (
timeFormat = "2006/01/02 15:04:05"
)

// Level type
type Level uint32

const (
// PanicLvl level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLvl Level = iota
// FatalLvl level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLvl
// ErrorLvl level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLvl
// WarnLvl level. Non-critical entries that deserve eyes.
WarnLvl
// InfoLvl level. General operational entries about what's going on inside the
// application.
InfoLvl
// DebugLvl level. Usually only enabled when debugging. Very verbose logging.
DebugLvl
)

// Hook is an alias for the hook function
type Hook = func(MetaInfo) error

// Logger defines the logger to be used
type Logger struct {
Hooks []Hook
}

// nolint: gochecknoglobals
var logger Logger

// nolint: gochecknoinits
func init() {
log.SetFlags(0)
logger = Logger{}
}

// MetaInfo is the metadata of the log
type MetaInfo struct {
Msg string
Lvl Level
}

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

// Infof logs in info level with a format
func Infof(format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
Info(msg)
Info(fmt.Sprintf(format, v...))
}

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

// Warnf logs in warn level with a format
func Warnf(format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
Warn(msg)
Warn(fmt.Sprintf(format, v...))
}

// Debug logs in debug level
func Debug(v ...interface{}) {
nowStr := time.Now().Format(timeFormat)

execLine := ""
_, file, line, ok := runtime.Caller(1)
if ok {
execLine = fmt.Sprintf("%s:%d", filepath.Base(file), line)
}

log.Printf("%s%s %s %s %s%s", yellow, nowStr, execLine, "[DEBUG]", fmt.Sprint(v...), reset)
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: DebugLvl})
}

// Debugf logs in debug level with a format
func Debugf(format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
Debug(msg)
Debug(fmt.Sprintf(format, v...))
}

// IfErrorDiffNil logs Error if argument is not nil
// IfErrorDiffNil logs Error if argument is not nil. DEPRECATED
func IfErrorDiffNil(v interface{}) {
if v != nil {
Error(v)
}
}

// ErrorType logs the error if the argument is not nil
func ErrorType(err error) {
if err != nil {
Error(err)
}
}

// Error logs in error level
func Error(v ...interface{}) {
nowStr := time.Now().Format(timeFormat)
log.Printf("%s%s %s %s%s", red, nowStr, "[ERROR]", fmt.Sprint(v...), reset)
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: ErrorLvl})
}

// Errorf logs in error level with a format
func Errorf(format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
Error(msg)
Error(fmt.Sprintf(format, v...))
}

// Fatalf logs in error level with a format
func Fatalf(format string, v ...interface{}) {
log.Fatalf(format, v...)
Fatal(fmt.Sprintf(format, v...))
}

// Fatal logs in error level
func Fatal(v ...interface{}) {
log.Fatal(v...)
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: FatalLvl})
}

// Panic logs in error level with a posterior Panic()
func Panic(v ...interface{}) {
log.Panic(v...)
exec(MetaInfo{Msg: fmt.Sprint(v...), Lvl: PanicLvl})
}

// Panicf logs in error level with a posterior Panic() with format
func Panicf(format string, v ...interface{}) {
Panic(fmt.Sprintf(format, v...))
}

func (li *MetaInfo) log() {
nowStr := time.Now().Format(timeFormat)
switch li.Lvl {
case PanicLvl:
log.Panic(li.Msg)
case FatalLvl:
log.Fatal(li.Msg)
case ErrorLvl:
log.Printf("%s%s %s %s%s", red, nowStr, "[ERROR]", li.Msg, reset)
case WarnLvl:
log.Printf("%s%s %s %s%s", yellow, nowStr, "[WARN]", li.Msg, reset)
case InfoLvl:
log.Printf("%s%s %s %s%s", green, nowStr, "[INFO]", li.Msg, reset)
case DebugLvl:
execLine := ""
_, file, line, ok := runtime.Caller(1)
if ok {
execLine = fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
log.Printf("%s%s %s %s %s%s", yellow, nowStr, execLine, "[DEBUG]", li.Msg, reset)
}
}

func exec(li MetaInfo) {
li.log()
for _, hook := range logger.Hooks {
nowStr := time.Now().Format(timeFormat)
if err := hook(li); err != nil {
log.Printf("%s%s %s %v%s", red, nowStr, "[ERROR]", err, reset)
}
}
}
21 changes: 21 additions & 0 deletions log/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package log

import "github.com/agflow/tools/notification/slack"

// NewSlackHook returns a hook for slack
func NewSlackHook(token string) Hook {
return func(info MetaInfo) error {
var color string
switch info.Lvl {
case InfoLvl:
color = slack.ColorGood
case FatalLvl, PanicLvl, ErrorLvl:
color = slack.ColorDanger
default:
color = slack.ColorWarning
}
msg := info.Msg + "\n"
slackCli := slack.New(token, true)
return slackCli.SendWithColor("feed-worker", msg, color)
}
}
6 changes: 6 additions & 0 deletions notification/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package notification

// Service is an interface of notification.Service
type Service interface {
Send(string, string) error
}
69 changes: 69 additions & 0 deletions notification/slack/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package slack

import (
"github.com/slack-go/slack"

"github.com/agflow/tools/agerr"
)

const (
defaultChannel = "test-notifications"
// ColorGood is slack's color "good"
ColorGood = "good"
// ColorWarning is slack's color "warning"
ColorWarning = "warning"
// ColorDanger is slack's color "danger"
ColorDanger = "danger"
)

// Client is wrapper of a slack.Client
type Client struct {
slackCli *slack.Client
enabled bool
}

// New return a new notifications/slack.Client
func New(token string, enabled bool) *Client {
return &Client{slackCli: slack.New(token), enabled: enabled}
}

func getChannel(channel string) string {
if channel == "" {
return defaultChannel
}
return channel
}

// Send sends a notification message to slack
func (c *Client) Send(channel, msg string) error {
if !c.enabled {
return nil
}

channel = getChannel(channel)

_, _, err := c.slackCli.PostMessage(
channel,
slack.MsgOptionAsUser(true),
slack.MsgOptionText(msg, false))
return agerr.Wrap("can't send slack notification: %w", err)
}

// SendWithColor sends a notification message to slack as an attachment with color
func (c *Client) SendWithColor(channel, msg, color string) error {
if !c.enabled {
return nil
}

channel = getChannel(channel)
attachment := slack.Attachment{
Text: msg,
Color: color,
}

_, _, err := c.slackCli.PostMessage(channel,
slack.MsgOptionAttachments(attachment),
slack.MsgOptionAsUser(true),
)
return agerr.Wrap("can't send slack notification with color: %w", err)
}
18 changes: 18 additions & 0 deletions notification/slack/slack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package slack

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestSend(t *testing.T) {
slackCli := New("xoxb-2314993037-3480243399810-eKklHxCNGvH7E3dnGaknRpZL", true)
require.Nil(t, slackCli.Send(defaultChannel, "this is a test notification"))
require.Nil(t, slackCli.SendWithColor(
defaultChannel,
"this is a test notification with a color",
ColorGood,
),
)
}
2 changes: 1 addition & 1 deletion sql/db/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Client struct {
DB *sql.DB
}

// Service is an interface od db.Service
// Service is an interface of db.Service
type Service interface {
Select(interface{}, string, ...interface{}) error
Close() error
Expand Down

0 comments on commit 3bbbe7e

Please sign in to comment.