go-kit/herror/stacktrace.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
}