-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstack.go
155 lines (126 loc) · 3.11 KB
/
stack.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
153
154
155
package wrapperr
import (
"encoding/json"
"fmt"
"runtime"
"strings"
)
type Stack []Annotation
func (stack Stack) String() string {
var lines []string
for _, note := range stack {
lines = append(lines, note.String())
}
return strings.Join(lines, "\n>> ")
}
// WithStack wraps the error inside another error. The original error is the root cause and
// can be unwrapped using the standard errors.Unwrap(...) function. The wrapper error contains
// the call stack in addition to the original error. When printed, it provides the call stack and
// the cause in text format. If it gets marshaled as JSON, it will include the call stack
// as a collection of calls. Each call information includes the code file path, line number
// and the function name and package and the annotation (if provided).
func WithStack(err error, messages ...string) error {
var msg string
if len(messages) > 0 {
msg = strings.Join(messages, " ")
}
return makeErr(err, msg)
}
func WithStackf(err error, format string, args ...interface{}) error {
msg := fmt.Sprintf(format, args...)
return makeErr(err, msg)
}
func makeErr(err error, msg string) error {
switch x := err.(type) {
case TracedErr:
if msg != "" {
calls := mark(defaultCallerStackSkip)
for i, note := range x.Stack {
if note.Loc.Func == calls[0].Func {
note.Message = msg
x.Stack[i] = note
break
}
}
}
return x
default:
}
calls := mark(defaultCallerStackSkip)
stack := make(Stack, len(calls))
for i, call := range calls {
stack[i] = Annotation{
Loc: call,
}
}
if msg != "" {
stack[0].Message = msg
}
return TracedErr{
Stack: stack,
Cause: err,
}
}
type TracedErr struct {
Stack Stack `json:"stack,omitempty"`
Cause error `json:"cause,omitempty"`
}
func (terr TracedErr) Error() string {
return "stack: " + terr.Stack.String() + "\ncause: " + fmt.Sprint(terr.Cause)
}
func (terr TracedErr) Unwrap() error { return terr.Cause }
func (terr TracedErr) MarshalJSON() ([]byte, error) {
var payload struct {
Stack Stack `json:"stack,omitempty"`
Cause interface{} `json:"cause,omitempty"`
}
payload.Stack = terr.Stack
cause := terr.Unwrap()
if cause != nil {
js, err := json.Marshal(cause)
switch {
case err != nil:
payload.Cause = cause.Error()
case string(js) == "{}":
payload.Cause = cause.Error()
default:
payload.Cause = cause
}
}
return json.Marshal(payload)
}
func mark(skip int) []Loc {
var pfuncs [maxCallerDepth]uintptr
n := runtime.Callers(skip, pfuncs[:])
var calls []uintptr
if n < maxCallerDepth {
calls = pfuncs[0:n]
} else {
calls = pfuncs[:]
}
var result []Loc
frames := runtime.CallersFrames(calls)
for frame, ok := frames.Next(); ok; frame, ok = frames.Next() {
var funcName string
fn := runtime.FuncForPC(frame.PC)
if fn == nil {
funcName = NotAvailableFuncName
} else {
funcName = fn.Name()
}
loc := Loc{
File: shortFilePath(frame.File),
Line: frame.Line,
Func: funcName,
}
result = append(result, loc)
}
return result
}
const (
NotAvailableFuncName = "NOT_AVAILABLE"
)
const (
maxCallerDepth = 64
defaultCallerStackSkip = 4
)