152 lines
3.1 KiB
Go
152 lines
3.1 KiB
Go
|
package herror
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/build"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Stacktrace holds information about the frames of the stack.
|
||
|
type Stacktrace struct {
|
||
|
Frames []Frame `json:"frames,omitempty"`
|
||
|
}
|
||
|
|
||
|
// Frame represents parsed information from runtime.Frame
|
||
|
type Frame struct {
|
||
|
Function string `json:"function,omitempty"`
|
||
|
Type string `json:"type,omitempty"`
|
||
|
Package string `json:"package,omitempty"`
|
||
|
Filename string `json:"filename,omitempty"`
|
||
|
AbsPath string `json:"abs_path,omitempty"`
|
||
|
Line int `json:"line,omitempty"`
|
||
|
InApp bool `json:"in_app,omitempty"`
|
||
|
}
|
||
|
|
||
|
// FrameFilter represents function to filter frames
|
||
|
type FrameFilter func(Frame) bool
|
||
|
|
||
|
const unknown string = "unknown"
|
||
|
|
||
|
// NewStacktrace creates a stacktrace using `runtime.Callers`.
|
||
|
func NewStacktrace(filters ...FrameFilter) *Stacktrace {
|
||
|
pcs := make([]uintptr, 100)
|
||
|
n := runtime.Callers(1, pcs)
|
||
|
|
||
|
if n == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
frames := extractFrames(pcs[:n])
|
||
|
|
||
|
// default filter
|
||
|
frames = filterFrames(frames, func(f Frame) bool {
|
||
|
return f.Package == "runtime" || f.Package == "testing" ||
|
||
|
strings.HasSuffix(f.Package, "/herror")
|
||
|
})
|
||
|
|
||
|
for _, filter := range filters {
|
||
|
frames = filterFrames(frames, filter)
|
||
|
}
|
||
|
|
||
|
stacktrace := Stacktrace{
|
||
|
Frames: frames,
|
||
|
}
|
||
|
|
||
|
return &stacktrace
|
||
|
}
|
||
|
|
||
|
// NewFrame assembles a stacktrace frame out of `runtime.Frame`.
|
||
|
func NewFrame(f runtime.Frame) Frame {
|
||
|
abspath := unknown
|
||
|
filename := unknown
|
||
|
if f.File != "" {
|
||
|
abspath = f.File
|
||
|
_, filename = filepath.Split(f.File)
|
||
|
}
|
||
|
|
||
|
function := unknown
|
||
|
pkgname := unknown
|
||
|
typer := ""
|
||
|
if f.Function != "" {
|
||
|
pkgname, typer, function = deconstructFunctionName(f.Function)
|
||
|
}
|
||
|
|
||
|
inApp := func() bool {
|
||
|
out := strings.HasPrefix(abspath, build.Default.GOROOT) ||
|
||
|
strings.Contains(pkgname, "vendor")
|
||
|
return !out
|
||
|
}()
|
||
|
|
||
|
return Frame{
|
||
|
AbsPath: abspath,
|
||
|
Filename: filename,
|
||
|
Line: f.Line,
|
||
|
Package: pkgname,
|
||
|
Type: typer,
|
||
|
Function: function,
|
||
|
InApp: inApp,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func filterFrames(frames []Frame, filter FrameFilter) []Frame {
|
||
|
filtered := make([]Frame, 0, len(frames))
|
||
|
|
||
|
for _, frame := range frames {
|
||
|
if filter(frame) {
|
||
|
continue
|
||
|
}
|
||
|
filtered = append(filtered, frame)
|
||
|
}
|
||
|
|
||
|
return filtered
|
||
|
}
|
||
|
|
||
|
func extractFrames(pcs []uintptr) []Frame {
|
||
|
frames := make([]Frame, 0, len(pcs))
|
||
|
callersFrames := runtime.CallersFrames(pcs)
|
||
|
|
||
|
for {
|
||
|
callerFrame, more := callersFrames.Next()
|
||
|
frames = append([]Frame{
|
||
|
NewFrame(callerFrame),
|
||
|
}, frames...)
|
||
|
|
||
|
if !more {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return frames
|
||
|
}
|
||
|
|
||
|
func deconstructFunctionName(name string) (pkg string, typer string, function string) {
|
||
|
if i := strings.LastIndex(name, "/"); i != -1 {
|
||
|
pkg = name[:i]
|
||
|
function = name[i+1:]
|
||
|
|
||
|
if d := strings.Index(function, "."); d != -1 {
|
||
|
pkg = fmt.Sprint(pkg, "/", function[:d])
|
||
|
function = function[d+1:]
|
||
|
}
|
||
|
|
||
|
if o, c := strings.LastIndex(name, ".("), strings.LastIndex(name, ")."); o != -1 && c != -1 {
|
||
|
pkg = name[:o]
|
||
|
function = name[c+2:]
|
||
|
|
||
|
typer = name[o+2 : c]
|
||
|
if i := strings.Index(typer, "*"); i != -1 {
|
||
|
typer = typer[1:]
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if i := strings.LastIndex(name, "."); i != -1 {
|
||
|
pkg = name[:i]
|
||
|
function = name[i+1:]
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|