-
Notifications
You must be signed in to change notification settings - Fork 0
/
flaw.go
178 lines (164 loc) · 5.12 KB
/
flaw.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package flaw
import (
"fmt"
"runtime"
)
// P is shorthand for Record.Payload type
type P map[string]any
type Record struct {
Function string
Payload P
}
type StackTrace struct {
// Line is the file line number of the location in this frame.
// For non-leaf frames, this will be the location of a call.
// This may be zero, if not known.
Line int
// File is the file name of the location in this frame.
// For non-leaf frames, this will be the location of a call.
// This may be the empty string if not known.
File string
// Function is the package path-qualified function name of
// this call frame. If non-empty, this string uniquely
// identifies a single function in the program.
// This may be the empty string if not known.
Function string
}
type JoinedError struct {
// Message is the result of joined error Error method call.
Message string
// CallerStackTrace is the error generator stack trace,
// which in a very rare case can be nil. See [runtime.CallersFrames],
// and [runtime.Callers] for more information on when this might happen.
CallerStackTrace *StackTrace
// TypeName is the type name of the error, which is the result of calling [fmt.Sprintf("%T", err)].
TypeName string
// SyntaxRepr is the string representation of the error, which is the result of calling [fmt.Sprintf("%+#v", err)].
SyntaxRepr string
}
type Flaw struct {
// Inner is the error string that was passed in during initialization.
Inner string
// InnerType is the type name of the error, which is the result of calling [fmt.Sprintf("%T", err)].
InnerType string
// InnerSyntaxRepr is the string representation of the error, which is the result of calling [fmt.Sprintf("%+#v", err)].
InnerSyntaxRepr string
// JoinedErrors is the list of optional (nil-able) errors
// joined into the error while traversing the stack back,
// usually in the same function initiated the error.
JoinedErrors []JoinedError
// Records contains contextual information in order the error traversed the stack up,
// i.e., the first item in the slice is the first record attached to the error,
// and the last item is the most recent attached record.
Records []Record
StackTrace []StackTrace
}
// Error satisfies builtin error interface type. It returns the inner error string.
func (f *Flaw) Error() string {
return f.Inner
}
func traces() []StackTrace {
const depth = 64
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
if n == 0 {
return nil
}
frames := runtime.CallersFrames(pcs[:n])
st := make([]StackTrace, 0, n)
for {
frame, ok := frames.Next()
st = append(st, StackTrace{
Line: frame.Line,
File: frame.File,
Function: frame.Function,
})
if !ok {
break
}
}
return st
}
func joinTrace() *StackTrace {
var pc [1]uintptr
n := runtime.Callers(3, pc[:])
if n == 0 {
return nil
}
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return &StackTrace{
Line: frame.Line,
File: frame.File,
Function: frame.Function,
}
}
func callerFunc() string {
const depth = 2
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
frames := runtime.CallersFrames(pcs[:n])
frame, _ := frames.Next()
return frame.Function
}
func newFlawWithoutTrace(err error) *Flaw {
return &Flaw{
Records: nil,
Inner: err.Error(),
InnerType: fmt.Sprintf("%T", err),
InnerSyntaxRepr: fmt.Sprintf("%+#v", err),
StackTrace: nil,
}
}
// From creates a [Flaw] instance from an existing error. You can append contextual
// information to it using the [Flaw.Append] function immediately after instantiation,
// or by the caller function, after making sure the returned error is of type [Flaw]
// (using [errors.As]), It panics if err is nil.
func From(err error) *Flaw {
if nil == err {
panic("err can not be nil")
}
f := newFlawWithoutTrace(err)
f.StackTrace = traces()
return f
}
// Append appends contextual information to [Flaw] instance. It can be called immediately
// after instantiation using [From], or by the parent caller function, after making sure
// the returned error is of type [Flaw] (using [errors.As]). It panics if payload is nil.
// Elements in payloads will be merged into payload in order they are provided,
// thus duplicate keys will be overwritten.
func (f *Flaw) Append(payload P, payloads ...P) *Flaw {
if nil == payload {
panic("payload must not be nil")
}
merged := make(P, len(payload))
for k, v := range payload {
merged[k] = v
}
for _, p := range payloads {
for k, v := range p {
merged[k] = v
}
}
f.Records = append(f.Records, Record{
Function: callerFunc(),
Payload: merged,
})
return f
}
// Join joins the error to the flaw as a JoinedError item.
// Usually, used in failed defer calls where the deferred function
// fails with another error, and it is desired to capture
// the information of the error, and attach it to the original error.
func (f *Flaw) Join(err error) *Flaw {
if nil == err {
panic("err must not be nil")
}
f.JoinedErrors = append(f.JoinedErrors, JoinedError{
Message: err.Error(),
CallerStackTrace: joinTrace(),
TypeName: fmt.Sprintf("%T", err),
SyntaxRepr: fmt.Sprintf("%+#v", err),
})
return f
}