86 lines
2.2 KiB
Go
86 lines
2.2 KiB
Go
|
package merr
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"text/tabwriter"
|
||
|
)
|
||
|
|
||
|
// MaxStackSize indicates the maximum number of stack frames which will be
|
||
|
// stored when embedding stack traces in errors.
|
||
|
var MaxStackSize = 50
|
||
|
|
||
|
const attrKeyStack attrKey = "stack"
|
||
|
|
||
|
// Stack represents a stack trace at a particular point in execution.
|
||
|
type Stack []uintptr
|
||
|
|
||
|
// Frame returns the first frame in the stack.
|
||
|
func (s Stack) Frame() runtime.Frame {
|
||
|
if len(s) == 0 {
|
||
|
panic("cannot call Frame on empty stack")
|
||
|
}
|
||
|
|
||
|
frame, _ := runtime.CallersFrames([]uintptr(s)).Next()
|
||
|
return frame
|
||
|
}
|
||
|
|
||
|
// Frames returns all runtime.Frame instances for this stack.
|
||
|
func (s Stack) Frames() []runtime.Frame {
|
||
|
if len(s) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
out := make([]runtime.Frame, 0, len(s))
|
||
|
frames := runtime.CallersFrames([]uintptr(s))
|
||
|
for {
|
||
|
frame, more := frames.Next()
|
||
|
out = append(out, frame)
|
||
|
if !more {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// String returns the full stack trace.
|
||
|
func (s Stack) String() string {
|
||
|
sb := strBuilderPool.Get().(*strings.Builder)
|
||
|
defer putStrBuilder(sb)
|
||
|
tw := tabwriter.NewWriter(sb, 0, 4, 4, ' ', 0)
|
||
|
for _, frame := range s.Frames() {
|
||
|
file := fmt.Sprintf("%s:%d", frame.File, frame.Line)
|
||
|
fmt.Fprintf(tw, "%s\t%s\n", file, frame.Function)
|
||
|
}
|
||
|
if err := tw.Flush(); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
func setStack(er *err, skip int) {
|
||
|
stackSlice := make([]uintptr, MaxStackSize)
|
||
|
// incr skip once for setStack, and once for runtime.Callers
|
||
|
l := runtime.Callers(skip+2, stackSlice)
|
||
|
er.attr[attrKeyStack] = val{val: Stack(stackSlice[:l]), visible: true}
|
||
|
}
|
||
|
|
||
|
// WithStack returns a copy of the original error, automatically wrapping it if
|
||
|
// the error is not from merr (see Wrap). The returned error has the embedded
|
||
|
// stacktrace set to the frame calling this function.
|
||
|
//
|
||
|
// skip can be used to exclude that many frames from the top of the stack.
|
||
|
func WithStack(e error, skip int) error {
|
||
|
er := wrap(e, true, -1)
|
||
|
setStack(er, skip+1)
|
||
|
return er
|
||
|
}
|
||
|
|
||
|
// GetStack returns the Stack which was embedded in the error, if the error is
|
||
|
// from this package. If not then nil is returned.
|
||
|
func GetStack(e error) Stack {
|
||
|
stack, _ := wrap(e, false, -1).attr[attrKeyStack].val.(Stack)
|
||
|
return stack
|
||
|
}
|