-
Notifications
You must be signed in to change notification settings - Fork 3
/
airbraker.go
130 lines (112 loc) · 3.11 KB
/
airbraker.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
// Package errornotifier is a notifier "provider" that provides
// a way to report runtime error. It uses gobrake notifier
// by default.
package errornotifier
import (
"errors"
"fmt"
"io"
"net/http"
"regexp"
"runtime/debug"
"strings"
"github.com/airbrake/gobrake/v5"
"github.com/theplant/appkit/log"
)
// Notifier defines an interface for reporting error
type Notifier interface {
Notify(err interface{}, r *http.Request, context map[string]interface{})
}
// airbrakeNotifier is a Notifier that sends messages to Airbrake,
// constructed via NewAirbrakeNotifier
type airbrakeNotifier struct {
notifier *gobrake.Notifier
}
func (n *airbrakeNotifier) Notify(e interface{}, req *http.Request, context map[string]interface{}) {
notice := n.notifier.Notice(e, req, 1)
for k, v := range context {
notice.Context[k] = v
}
n.notifier.SendNoticeAsync(notice)
}
// AirbrakeConfig is struct to embed into application config to allow
// configuration of Airbrake notifier from environment or other
// external source
type AirbrakeConfig struct {
ProjectID int64
Token string
Environment string `default:"dev"`
KeysBlocklist []interface{}
Filters []interface{}
}
var defaultKeysBlocklist = []interface{}{
"Authorization",
}
// NewAirbrakeNotifier constructs Airbrake notifier from given config
//
// Returns error if no Airbrake configuration or airbrake
// configuration is invalid
//
// Notify is async, call close to wait send data to Airbrake.
func NewAirbrakeNotifier(c AirbrakeConfig) (Notifier, io.Closer, error) {
if c.Token == "" {
return nil, nil, errors.New("blank Airbrake token")
}
if c.ProjectID <= 0 {
return nil, nil, fmt.Errorf("invalid Airbrake project id: %d", c.ProjectID)
}
if c.KeysBlocklist == nil {
c.KeysBlocklist = defaultKeysBlocklist
}
notifier := gobrake.NewNotifierWithOptions(&gobrake.NotifierOptions{
ProjectId: c.ProjectID,
ProjectKey: c.Token,
Environment: c.Environment,
KeysBlocklist: c.KeysBlocklist,
})
notifier.AddFilter(func(notice *gobrake.Notice) *gobrake.Notice {
message := notice.Errors[0].Message
for _, filter := range c.Filters {
switch filter := filter.(type) {
case string:
if strings.Contains(message, filter) {
return nil
}
case *regexp.Regexp:
if filter.MatchString(message) {
return nil
}
default:
panic(fmt.Errorf("unsupported filter key type: %T", filter))
}
}
return notice
})
return &airbrakeNotifier{notifier: notifier}, notifier, nil
}
type logNotifier struct {
logger log.Logger
}
// Notify is part of Notifier interface
func (n *logNotifier) Notify(val interface{}, req *http.Request, context map[string]interface{}) {
logger := n.logger
if req != nil {
l, ok := log.FromContext(req.Context())
if ok {
logger = l
}
}
_ = logger.Error().Log(
"err", val,
"context", fmt.Sprint(context),
"msg", fmt.Sprintf("error notification: %v", val),
"stacktrace", string(debug.Stack()),
)
}
// NewLogNotifier constructs notifier that logs error notification
// messages to given logger
func NewLogNotifier(logger log.Logger) Notifier {
return &logNotifier{
logger: logger,
}
}