-
Notifications
You must be signed in to change notification settings - Fork 1
/
catcher.go
141 lines (125 loc) · 3.48 KB
/
catcher.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
// Package catcher provides utilites to gracefully handle crap software.
package catcher
import (
"fmt"
"sync"
)
var (
callerOffset int = 0
mu sync.RWMutex
notifiers []NotifierFunc
)
// RegisterNotifiers adds all listed notifiers to a global list, each of them
// would be invoked upon an error occurs.
func RegisterNotifiers(fn ...NotifierFunc) {
mu.Lock()
notifiers = append(notifiers, fn...)
mu.Unlock()
}
// SetCallerOffset allows to customize stacktrace reporting in case the catcher is wrapped.
// Default is 0. Setting to positive or negative will shift caller's stacktrace window up and down.
func SetCallerOffset(n int) {
mu.Lock()
callerOffset = n
mu.Unlock()
}
// Catch must be used together with defer to catch panics from suspicious functions.
// All listed receivers and all the global notifiers will be invoked with the
// error itself, function name and a stack trace.
func Catch(recv ...Receiver) {
if panicData := recover(); panicData != nil {
mu.RLock()
caller := getCaller(4+callerOffset) + " <= " + getCaller(5+callerOffset)
mu.RUnlock()
if err, ok := panicData.(error); ok {
if len(recv) > 0 {
stack := getStack()
for _, r := range recv {
r.RecvError(err, nil, caller, stack)
}
}
return
}
err := fmt.Errorf("%+v", panicData)
if len(recv) > 0 {
// invoke receivers
stack := getStack()
for _, r := range recv {
r.RecvPanic(err, nil, caller, stack)
}
}
mu.RLock()
defer mu.RUnlock()
if len(notifiers) > 0 {
wg := new(sync.WaitGroup)
// notify all registered notifiers
for _, fn := range notifiers {
wg.Add(1)
go func(fn NotifierFunc) {
fn(err)
wg.Done()
}(fn)
}
wg.Wait()
}
}
}
// CatchWithContext must be used together with defer to catch panics from suspicious functions.
// All listed receivers and all the global notifiers will be invoked with the error itself,
// function name and a stack trace, along with the provided context and meta.
func CatchWithContext(context, meta interface{}, recv ...Receiver) {
if panicData := recover(); panicData != nil {
mu.RLock()
caller := getCaller(4+callerOffset) + " <= " + getCaller(5+callerOffset)
mu.RUnlock()
if err, ok := panicData.(error); ok {
if len(recv) > 0 {
stack := getStack()
for _, r := range recv {
r.RecvError(err, context, caller, stack)
}
}
return
}
err := fmt.Errorf("%+v", panicData)
if len(recv) > 0 {
stack := getStack()
// invoke receivers
for _, r := range recv {
r.RecvPanic(err, context, caller, stack)
}
}
mu.RLock()
defer mu.RUnlock()
if len(notifiers) > 0 {
wg := new(sync.WaitGroup)
// notify all registered notifiers
for _, fn := range notifiers {
wg.Add(1)
go func(fn NotifierFunc) {
fn(err, context, meta)
wg.Done()
}(fn)
}
wg.Wait()
}
}
}
// NotifierFunc is a func that may be used as a global notifier (via RegisterNotifiers()).
type NotifierFunc func(err error, rawData ...interface{}) error
// Receiver handles panics and errors thrown with a panic.
type Receiver interface {
RecvError(err error, context interface{}, caller string, stack []byte)
RecvPanic(err error, context interface{}, caller string, stack []byte)
}
// Error has additional context and stack that can be accessed outside receivers;
// by storing it to a variable using the RecvError receiver.
type Error struct {
Err error
Context interface{}
Caller string
Stack []byte
}
func (e Error) Error() string {
return e.Err.Error()
}