diff --git a/CHANGELOG.md b/CHANGELOG.md index 046f49c4..987d015b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +- Add trace origin to span data ([#849](https://github.com/getsentry/sentry-go/pull/849)) +- Add ability to skip frames in stacktrace ([#852](https://github.com/getsentry/sentry-go/pull/852)) + ## 0.28.1 The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.28.1. diff --git a/client.go b/client.go index f5069b71..28160af0 100644 --- a/client.go +++ b/client.go @@ -416,9 +416,13 @@ func (client *Client) Options() ClientOptions { return client.options } +type EventOptions struct { + SkipFrames int +} + // CaptureMessage captures an arbitrary message. -func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID { - event := client.EventFromMessage(message, LevelInfo) +func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier, opts ...EventOptions) *EventID { + event := client.EventFromMessage(message, LevelInfo, opts...) return client.CaptureEvent(event, hint, scope) } @@ -440,7 +444,7 @@ func (client *Client) CaptureCheckIn(checkIn *CheckIn, monitorConfig *MonitorCon // CaptureEvent captures an event on the currently active client if any. // -// The event must already be assembled. Typically code would instead use +// The event must already be assembled. Typically, code would instead use // the utility methods like CaptureException. The return value is the // event ID. In case Sentry is disabled or event was dropped, the return value will be nil. func (client *Client) CaptureEvent(event *Event, hint *EventHint, scope EventModifier) *EventID { @@ -449,7 +453,7 @@ func (client *Client) CaptureEvent(event *Event, hint *EventHint, scope EventMod // Recover captures a panic. // Returns EventID if successfully, or nil if there's no error to recover from. -func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModifier) *EventID { +func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModifier, opts ...EventOptions) *EventID { if err == nil { err = recover() } @@ -459,7 +463,7 @@ func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModif // is store the Context in the EventHint and there nil means the Context is // not available. // nolint: staticcheck - return client.RecoverWithContext(nil, err, hint, scope) + return client.RecoverWithContext(nil, err, hint, scope, opts...) } // RecoverWithContext captures a panic and passes relevant context object. @@ -469,6 +473,7 @@ func (client *Client) RecoverWithContext( err interface{}, hint *EventHint, scope EventModifier, + opts ...EventOptions, ) *EventID { if err == nil { err = recover() @@ -491,9 +496,9 @@ func (client *Client) RecoverWithContext( case error: event = client.EventFromException(err, LevelFatal) case string: - event = client.EventFromMessage(err, LevelFatal) + event = client.EventFromMessage(err, LevelFatal, opts...) default: - event = client.EventFromMessage(fmt.Sprintf("%#v", err), LevelFatal) + event = client.EventFromMessage(fmt.Sprintf("%#v", err), LevelFatal, opts...) } return client.CaptureEvent(event, hint, scope) } @@ -514,7 +519,7 @@ func (client *Client) Flush(timeout time.Duration) bool { } // EventFromMessage creates an event from the given message string. -func (client *Client) EventFromMessage(message string, level Level) *Event { +func (client *Client) EventFromMessage(message string, level Level, opts ...EventOptions) *Event { if message == "" { err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())} return client.EventFromException(err, level) @@ -525,7 +530,7 @@ func (client *Client) EventFromMessage(message string, level Level) *Event { if client.options.AttachStacktrace { event.Threads = []Thread{{ - Stacktrace: NewStacktrace(), + Stacktrace: NewStacktrace(opts...), Crashed: false, Current: true, }} diff --git a/stacktrace.go b/stacktrace.go index 2f8933ea..67ea8007 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -23,7 +23,7 @@ type Stacktrace struct { } // NewStacktrace creates a stacktrace using runtime.Callers. -func NewStacktrace() *Stacktrace { +func NewStacktrace(opts ...EventOptions) *Stacktrace { pcs := make([]uintptr, 100) n := runtime.Callers(1, pcs) @@ -31,8 +31,13 @@ func NewStacktrace() *Stacktrace { return nil } + skipFrames := 0 + if len(opts) > 0 { + skipFrames = opts[0].SkipFrames + } + runtimeFrames := extractFrames(pcs[:n]) - frames := createFrames(runtimeFrames) + frames := createFrames(runtimeFrames, skipFrames) stacktrace := Stacktrace{ Frames: frames, @@ -62,7 +67,7 @@ func ExtractStacktrace(err error) *Stacktrace { } runtimeFrames := extractFrames(pcs) - frames := createFrames(runtimeFrames) + frames := createFrames(runtimeFrames, 0) stacktrace := Stacktrace{ Frames: frames, @@ -270,30 +275,25 @@ func extractFrames(pcs []uintptr) []runtime.Frame { for { callerFrame, more := callersFrames.Next() - frames = append(frames, callerFrame) + // Prepend the frame + frames = append([]runtime.Frame{callerFrame}, frames...) if !more { break } } - // TODO don't append and reverse, put in the right place from the start. - // reverse - for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 { - frames[i], frames[j] = frames[j], frames[i] - } - return frames } // createFrames creates Frame objects while filtering out frames that are not // meant to be reported to Sentry, those are frames internal to the SDK or Go. -func createFrames(frames []runtime.Frame) []Frame { - if len(frames) == 0 { +func createFrames(frames []runtime.Frame, skip int) []Frame { + if len(frames) == 0 || skip >= len(frames) { return nil } - result := make([]Frame, 0, len(frames)) + var result []Frame for _, frame := range frames { function := frame.Function @@ -307,7 +307,11 @@ func createFrames(frames []runtime.Frame) []Frame { } } - return result + if skip >= len(result) { + return []Frame{} + } + + return result[:len(result)-skip] } // TODO ID: why do we want to do this? @@ -333,12 +337,12 @@ func shouldSkipFrame(module string) bool { var goRoot = strings.ReplaceAll(build.Default.GOROOT, "\\", "/") func setInAppFrame(frame *Frame) { + frame.InApp = true + if strings.HasPrefix(frame.AbsPath, goRoot) || strings.Contains(frame.Module, "vendor") || strings.Contains(frame.Module, "third_party") { frame.InApp = false - } else { - frame.InApp = true } } diff --git a/stacktrace_test.go b/stacktrace_test.go index 96d68280..663f86bc 100644 --- a/stacktrace_test.go +++ b/stacktrace_test.go @@ -139,7 +139,7 @@ func TestCreateFrames(t *testing.T) { } for _, tt := range tests { t.Run("", func(t *testing.T) { - got := createFrames(tt.in) + got := createFrames(tt.in, 0) if diff := cmp.Diff(tt.out, got); diff != "" { t.Errorf("filterFrames() mismatch (-want +got):\n%s", diff) }