-
Notifications
You must be signed in to change notification settings - Fork 0
/
panic.go
123 lines (102 loc) · 2.63 KB
/
panic.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
package logger
import (
"errors"
"fmt"
"runtime/debug"
"strings"
)
// PanicError is the data used to capture any function returning an error
// and any generated panic. If Err is set, means that the function returned
// an error without panicking, instead if PanicErr is set, this means that
// the function returned prematurely due to a panic: the panic error is set
// and the stack trace is provided in the relative field
type PanicError struct {
err error
panicErr error
stack string
}
func (p PanicError) Err() error {
return p.err
}
func (p PanicError) PanicErr() error {
return p.panicErr
}
func (p PanicError) Stack() string {
return p.stack
}
// PanicToErr runs any function that returns an error in a panic-controlled
// environment and converts any panic (if any) into an error. Expecially, the
// error generated by a panic can be unwrapped to get rid of the stack trace (
// or manipulated to separate the error from the stack trace)
func CapturePanic(f func() error) (panicErr *PanicError) {
defer func() {
if err := recover(); err != nil {
switch err := err.(type) {
case error:
panicErr = &PanicError {
panicErr: fmt.Errorf("%w", err),
stack: Stack(),
}
default:
panicErr = &PanicError {
panicErr: fmt.Errorf("%v", err),
stack: Stack(),
}
}
}
}()
if err := f(); err != nil {
panicErr = &PanicError{err: err}
}
return
}
// CapturePanic is the same as PanicToErr but returns a plain error
func PanicToErr(f func() error) error {
panicErr := CapturePanic(f)
if panicErr == nil || panicErr.error() == nil {
return nil
}
return panicErr
}
func (err PanicError) error() error {
if err.panicErr != nil {
return fmt.Errorf("%w\n%s", err.panicErr, IndentString(err.stack, 2))
}
return err.err
}
func (err PanicError) Error() string {
return err.error().Error()
}
func (err PanicError) Unwrap() error {
if err.panicErr != nil {
return err.panicErr
}
unwrap := errors.Unwrap(err.err)
if unwrap != nil {
return unwrap
}
return err.err
}
func (err PanicError) String() string {
return err.Error()
}
// Stack returns the execution stack. This must be called after
// a panic (during a recovery) because it strips the first lines
// where are reported also the recovery functions, returning only
// the panic-reletate stuff. If this is not desired just use the
// standard debug.Stack
func Stack() string {
var out string
split := strings.Split(string(debug.Stack()), "\n")
cont := true
for _, s := range split {
if strings.HasPrefix(s, "panic(") {
cont = false
}
if cont {
continue
}
out += s + "\n"
}
return strings.TrimRight(out, "\n")
}