| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- package sentry
- import (
- "go/build"
- "path/filepath"
- "reflect"
- "regexp"
- "runtime"
- "strings"
- )
- const unknown string = "unknown"
- // The module download is split into two parts: downloading the go.mod and downloading the actual code.
- // If you have dependencies only needed for tests, then they will show up in your go.mod,
- // and go get will download their go.mods, but it will not download their code.
- // The test-only dependencies get downloaded only when you need it, such as the first time you run go test.
- //
- // https://github.com/golang/go/issues/26913#issuecomment-411976222
- // Stacktrace holds information about the frames of the stack.
- type Stacktrace struct {
- Frames []Frame `json:"frames,omitempty"`
- FramesOmitted []uint `json:"frames_omitted,omitempty"`
- }
- // NewStacktrace creates a stacktrace using `runtime.Callers`.
- func NewStacktrace() *Stacktrace {
- pcs := make([]uintptr, 100)
- n := runtime.Callers(1, pcs)
- if n == 0 {
- return nil
- }
- frames := extractFrames(pcs[:n])
- frames = filterFrames(frames)
- stacktrace := Stacktrace{
- Frames: frames,
- }
- return &stacktrace
- }
- // ExtractStacktrace creates a new `Stacktrace` based on the given `error` object.
- // TODO: Make it configurable so that anyone can provide their own implementation?
- // Use of reflection allows us to not have a hard dependency on any given package, so we don't have to import it
- func ExtractStacktrace(err error) *Stacktrace {
- method := extractReflectedStacktraceMethod(err)
- if !method.IsValid() {
- return nil
- }
- pcs := extractPcs(method)
- if len(pcs) == 0 {
- return nil
- }
- frames := extractFrames(pcs)
- frames = filterFrames(frames)
- stacktrace := Stacktrace{
- Frames: frames,
- }
- return &stacktrace
- }
- func extractReflectedStacktraceMethod(err error) reflect.Value {
- var method reflect.Value
- // https://github.com/pingcap/errors
- methodGetStackTracer := reflect.ValueOf(err).MethodByName("GetStackTracer")
- // https://github.com/pkg/errors
- methodStackTrace := reflect.ValueOf(err).MethodByName("StackTrace")
- // https://github.com/go-errors/errors
- methodStackFrames := reflect.ValueOf(err).MethodByName("StackFrames")
- if methodGetStackTracer.IsValid() {
- stacktracer := methodGetStackTracer.Call(make([]reflect.Value, 0))[0]
- stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
- if stacktracerStackTrace.IsValid() {
- method = stacktracerStackTrace
- }
- }
- if methodStackTrace.IsValid() {
- method = methodStackTrace
- }
- if methodStackFrames.IsValid() {
- method = methodStackFrames
- }
- return method
- }
- func extractPcs(method reflect.Value) []uintptr {
- var pcs []uintptr
- stacktrace := method.Call(make([]reflect.Value, 0))[0]
- if stacktrace.Kind() != reflect.Slice {
- return nil
- }
- for i := 0; i < stacktrace.Len(); i++ {
- pc := stacktrace.Index(i)
- if pc.Kind() == reflect.Uintptr {
- pcs = append(pcs, uintptr(pc.Uint()))
- continue
- }
- if pc.Kind() == reflect.Struct {
- field := pc.FieldByName("ProgramCounter")
- if field.IsValid() && field.Kind() == reflect.Uintptr {
- pcs = append(pcs, uintptr(field.Uint()))
- continue
- }
- }
- }
- return pcs
- }
- // https://docs.sentry.io/development/sdk-dev/interfaces/stacktrace/
- type Frame struct {
- Function string `json:"function,omitempty"`
- Symbol string `json:"symbol,omitempty"`
- Module string `json:"module,omitempty"`
- Package string `json:"package,omitempty"`
- Filename string `json:"filename,omitempty"`
- AbsPath string `json:"abs_path,omitempty"`
- Lineno int `json:"lineno,omitempty"`
- Colno int `json:"colno,omitempty"`
- PreContext []string `json:"pre_context,omitempty"`
- ContextLine string `json:"context_line,omitempty"`
- PostContext []string `json:"post_context,omitempty"`
- InApp bool `json:"in_app,omitempty"`
- Vars map[string]interface{} `json:"vars,omitempty"`
- }
- // NewFrame assembles a stacktrace frame out of `runtime.Frame`.
- func NewFrame(f runtime.Frame) Frame {
- abspath := f.File
- filename := f.File
- function := f.Function
- var module string
- if filename != "" {
- filename = extractFilename(filename)
- } else {
- filename = unknown
- }
- if abspath == "" {
- abspath = unknown
- }
- if function != "" {
- module, function = deconstructFunctionName(function)
- }
- frame := Frame{
- AbsPath: abspath,
- Filename: filename,
- Lineno: f.Line,
- Module: module,
- Function: function,
- }
- frame.InApp = isInAppFrame(frame)
- return frame
- }
- func extractFrames(pcs []uintptr) []Frame {
- var frames []Frame
- callersFrames := runtime.CallersFrames(pcs)
- for {
- callerFrame, more := callersFrames.Next()
- frames = append([]Frame{
- NewFrame(callerFrame),
- }, frames...)
- if !more {
- break
- }
- }
- return frames
- }
- func filterFrames(frames []Frame) []Frame {
- isTestFileRegexp := regexp.MustCompile(`getsentry/sentry-go/.+_test.go`)
- isExampleFileRegexp := regexp.MustCompile(`getsentry/sentry-go/example/`)
- filteredFrames := make([]Frame, 0, len(frames))
- for _, frame := range frames {
- // go runtime frames
- if frame.Module == "runtime" || frame.Module == "testing" {
- continue
- }
- // sentry internal frames
- isTestFile := isTestFileRegexp.MatchString(frame.AbsPath)
- isExampleFile := isExampleFileRegexp.MatchString(frame.AbsPath)
- if strings.Contains(frame.AbsPath, "github.com/getsentry/sentry-go") &&
- !isTestFile &&
- !isExampleFile {
- continue
- }
- filteredFrames = append(filteredFrames, frame)
- }
- return filteredFrames
- }
- func extractFilename(path string) string {
- _, file := filepath.Split(path)
- return file
- }
- func isInAppFrame(frame Frame) bool {
- if strings.HasPrefix(frame.AbsPath, build.Default.GOROOT) ||
- strings.Contains(frame.Module, "vendor") ||
- strings.Contains(frame.Module, "third_party") {
- return false
- }
- return true
- }
- // Transform `runtime/debug.*T·ptrmethod` into `{ module: runtime/debug, function: *T.ptrmethod }`
- func deconstructFunctionName(name string) (module string, function string) {
- if idx := strings.LastIndex(name, "."); idx != -1 {
- module = name[:idx]
- function = name[idx+1:]
- }
- function = strings.Replace(function, "·", ".", -1)
- return module, function
- }
- func callerFunctionName() string {
- pcs := make([]uintptr, 1)
- runtime.Callers(3, pcs)
- callersFrames := runtime.CallersFrames(pcs)
- callerFrame, _ := callersFrames.Next()
- _, function := deconstructFunctionName(callerFrame.Function)
- return function
- }
|