-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patherrors.go
More file actions
163 lines (147 loc) · 4.98 KB
/
Copy patherrors.go
File metadata and controls
163 lines (147 loc) · 4.98 KB
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
package serrors
import (
"bytes"
"errors"
"fmt"
"runtime"
"text/template"
)
// StackTracer defines an interface that's met by an error that returns a stacktrace. This is
// intended to be used by errors that capture the stacktrace to the source of the error. Each
// invocation of StackTrace() must return a new instance of *runtime.Frames, so that this method
// can be invoked more than once (runtime.Frames uses internal iteration and has no way to reset
// the iterator).
type StackTracer interface {
StackTrace() *runtime.Frames
}
// stackErr wraps an error with the stack location where the error occurred.
type stackErr struct {
err error
trace []uintptr
stackTracer StackTracer
}
// StackTrace returns the call stack frames for the StackErr. If this was the first StackTracer on
// the unwrap chain, it captures when the StackErr was instantiated. If there was an earlier StackTracer,
// the se.stackTracer field is set, and the StackTrace() is returned from it.
//
// A new instance of *runtime.Frames is created every time this method is run, since the struct tracks
// its own offset and cannot be reused.
func (se stackErr) StackTrace() *runtime.Frames {
if se.stackTracer != nil {
return se.stackTracer.StackTrace()
}
return runtime.CallersFrames(se.trace)
}
// Is implementation to properly handle two StackErr instances being compared to each other using errors.Is.
// Both StackErr instances need to be unwrapped because the trace slice field makes the StackErr not comparable.
func (se stackErr) Is(err error) bool {
if err, ok := err.(stackErr); ok {
return errors.Is(se.err, err.err)
}
return errors.Is(se.err, err)
}
// WithStack takes in an error and returns an error wrapped in a StackErr with the location where
// an error was first created or returned from third-party code. If there is already an error
// in the error chain that exposes a stacktrace via the StackTrace() method, WithStack returns
// the passed-in error. If a nil error is passed in, nil is returned.
func WithStack(err error) error {
if err == nil {
return nil
}
var se StackTracer
if errors.As(err, &se) {
return err
}
return stackErr{
err: err,
trace: buildStackTrace(),
}
}
func buildStackTrace() []uintptr {
pc := make([]uintptr, 20)
n := runtime.Callers(3, pc)
pc = pc[:n]
return pc
}
// New builds a StackErr out of a string.
func New(msg string) error {
return stackErr{
err: errors.New(msg),
trace: buildStackTrace(),
}
}
// Errorf wraps the error returned by fmt.Errorf in a StackErr. If there is an existing StackTracer
// in the unwrap chain, its stack trace will be preserved.
func Errorf(format string, vals ...interface{}) error {
err := fmt.Errorf(format, vals...)
// it's possible that there was already a StackTracer in the unwrap chain in the fmt.Errorf.
// if so, use that stacktracer in the StackErr.
var st StackTracer
if errors.As(err, &st) {
return stackErr{
err: err,
stackTracer: st,
}
}
return stackErr{
err: err,
trace: buildStackTrace(),
}
}
// Unwrap exposes the error wrapped by StackErr.
func (se stackErr) Unwrap() error {
return se.err
}
// Error is the marker interface for an error, it returns the wrapped error or an empty string if there is no
// wrapped error.
func (se stackErr) Error() string {
if se.err == nil {
return ""
}
return se.err.Error()
}
// StandardFormat is a one-line template used to convert a *runtime.Frame to a
// string. Each entry is formatted as:
//
// FUNCTION_NAME (FILE_NAME:LINE_NUMBER)
var StandardFormat = template.Must(template.New("standardFormat").Parse("{{.Function}} ({{.File}}:{{.Line}})"))
// PanicFormat is a template resembling the output of a `panic` used to convert
// a *runtime.Frame to a string. Each entry is formatted as:
//
// FUNCTION_NAME
// FILE_NAME:LINE_NUMBER
var PanicFormat = template.Must(template.New("standardFormat").Parse("{{.Function}}\n\t{{.File}}:{{.Line}}"))
// Trace returns the stack trace information as a slice of strings formatted using the provided Go template. The valid
// fields in the template are Function, File, and Line. See StandardFormat for an example.
func Trace(e error, t *template.Template) ([]string, error) {
var se StackTracer
if !errors.As(e, &se) {
return nil, nil
}
s := make([]string, 0, 20)
frames := se.StackTrace()
var b bytes.Buffer
for {
b.Reset()
frame, more := frames.Next()
err := t.Execute(&b, frame)
if err != nil {
return nil, WithStack(err)
}
s = append(s, b.String())
if !more {
break
}
}
return s, nil
}
// Sentinel is a way to turn a constant string into an error. It allows you to safely declare a
// package-level error so that it can't be accidentally modified to refer to a different value.
//
// Deprecated: For package-scoped errors or other errors that should not have
// stack traces, use the standard library's [errors.New].
type Sentinel string
// Error is the marker interface for an error. This converts a Sentinel into a string for output.
func (s Sentinel) Error() string {
return string(s)
}