-
Notifications
You must be signed in to change notification settings - Fork 0
/
errstack.go
152 lines (133 loc) · 3.77 KB
/
errstack.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
142
143
144
145
146
147
148
149
150
151
152
package errstack
import (
"fmt"
"io"
"runtime"
)
// ErrorStack is an error object with a stack trace and layers of context.
type ErrorStack struct {
msg string
cause error
stack StackTrace
}
// New creates a new ErrorStack with a single message and a new stack trace,
// pointing to the caller of New().
func New(msg string) ErrorStack {
return ErrorStack{
msg: msg,
stack: Callers(2),
}
}
// Errorf creates a new ErrorStack with a message generated from format and args.
func Errorf(format string, args ...interface{}) ErrorStack {
msg := fmt.Sprintf(format, args...)
return ErrorStack{
msg: msg,
stack: Callers(2),
}
}
func (err ErrorStack) Error() string {
return err.msg
}
// Cause returns the immediate predecessor in the chain of causation
// to this error, or nil if there is no known cause.
func (err ErrorStack) Cause() error {
return err.cause
}
func (err ErrorStack) StackTrace() StackTrace {
return err.stack
}
func (err ErrorStack) Format(state fmt.State, verb rune) {
switch verb {
case 'v':
if state.Flag('+') {
err.WriteStack(state)
return
}
fallthrough
case 's':
io.WriteString(state, err.msg)
case 'q':
fmt.Fprintf(state, "%q", err.msg)
}
}
// WriteStack writes the chain of stack traces in err to writer.
func (err ErrorStack) WriteStack(writer io.Writer) {
stopFunction := "runtime.main"
for _, line := range err.FormatStack(&stopFunction) {
writer.Write([]byte(line + "\n"))
}
}
// FormatStack returns the chain of stack traces as a slice of ready-to-print
// lines of text.
func (err ErrorStack) FormatStack(stopFunction *string) []string {
firstFunction := runtime.FuncForPC(err.stack[0]).Name()
lines := err.stack.FormatStack(err.msg, stopFunction)
// chain stack traces to the next underlying error
if esCause, ok := err.cause.(ErrorStack); ok {
lines = append(lines, "")
lines = append(lines, esCause.FormatStack(&firstFunction)...)
}
return lines
}
// WrapChain returns a new ErrorStack that wraps an existing error,
// adding a stack trace to it. If cause is already an ErrorStack,
// then the stack traces will be chained when the error is formatted.
func WrapChain(cause error, msg string) ErrorStack {
return wrap(cause, msg)
}
// WrapOptional returns cause unchanged if cause is already an ErrorStack.
// Otherwise, it wraps cause in a new ErrorStack that adds a stack trace.
func WrapOptional(cause error, msg string) ErrorStack {
// cause already has a stack trace: preserve it and ignore msg
if esCause, ok := cause.(ErrorStack); ok {
return esCause
}
return wrap(cause, msg)
}
// WrapTruncate returns cause with its stack trace truncated to the
// current caller, if cause is already an ErrorStack. Otherwise, it
// wraps cause in a new ErrorStack and adds a stack trace.
func WrapTruncate(cause error, msg string) ErrorStack {
if esCause, ok := cause.(ErrorStack); ok {
esCause.stack = Callers(2)
return esCause
}
return wrap(cause, msg)
}
func wrap(cause error, msg string) ErrorStack {
return ErrorStack{
msg: msg,
cause: cause,
stack: Callers(3),
}
}
// Wrap is an alias for WrapChain. If you want different default
// error-wrapping behaviour in your application, just replace Wrap
// with a different function (e.g. WrapOptional or WrapTruncate)
// during startup.
var Wrap = WrapChain
type Causer interface {
Cause() error
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements Causer.
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
var previous error
for err != nil {
cause, ok := err.(Causer)
if !ok {
break
}
previous = err
err = cause.Cause()
}
if err != nil {
return err
}
return previous
}