Skip to content

Commit

Permalink
Add ability to skip frames (#852)
Browse files Browse the repository at this point in the history
  • Loading branch information
ribice authored Jul 10, 2024
1 parent dafeb07 commit d95747f
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
23 changes: 14 additions & 9 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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 {
Expand All @@ -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()
}
Expand All @@ -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.
Expand All @@ -469,6 +473,7 @@ func (client *Client) RecoverWithContext(
err interface{},
hint *EventHint,
scope EventModifier,
opts ...EventOptions,
) *EventID {
if err == nil {
err = recover()
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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,
}}
Expand Down
36 changes: 20 additions & 16 deletions stacktrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,21 @@ 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)

if n == 0 {
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,
Expand Down Expand Up @@ -62,7 +67,7 @@ func ExtractStacktrace(err error) *Stacktrace {
}

runtimeFrames := extractFrames(pcs)
frames := createFrames(runtimeFrames)
frames := createFrames(runtimeFrames, 0)

stacktrace := Stacktrace{
Frames: frames,
Expand Down Expand Up @@ -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
Expand All @@ -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?
Expand All @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion stacktrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit d95747f

Please sign in to comment.