Only use the cause with a stack trace in GetOrNewStacktrace#167
Only use the cause with a stack trace in GetOrNewStacktrace#167martinxsliu wants to merge 1 commit intogetsentry:masterfrom
Conversation
|
Any news on merging this? I'm running into the same situation—I'm calling |
5ca3e67 to
64c4af3
Compare
|
There is a better way to do this. Sentry supports chained exceptions. Attached a screenshot of how this looks in Sentry UI. The class for sending chained exceptions is already defined. Instead of arguing whether we should capture a new stack trace, recover the last one or try to get a stack trace for the cause, why not send the whole chain? func exceptions(err error, includePaths []string) (es Exceptions) {
// build chained stacktrace,
// as long as errors have an underlying cause
for {
// we're trying to recover stack traces added by pkg/errors,
// it is meaningless at this point to create stack traces
// since we don't care about CaptureError caller
stackTrace := recoverPkgErrorsStacktrace(err, 3, includePaths)
es.Values = append(es.Values, NewException(err, stackTrace))
// reverse exceptions, this is the order sentry expects them
// see https://docs.sentry.io/clientdev/interfaces/exception/
for i := len(es.Values)/2 - 1; i >= 0; i-- {
opp := len(es.Values) - 1 - i
es.Values[i], es.Values[opp] = es.Values[opp], es.Values[i]
}
if withCause, ok := err.(causer); ok {
err = withCause.Cause()
} else {
break
}
}
// if newest error does not have a stack trace, we're creating one
// it will be point to location where raven.Capture was called
last := es.Values[len(es.Values)-1]
if last.Stacktrace == nil {
last.Stacktrace = NewStacktrace(3, 3, includePaths)
}
return
}Where func recoverPkgErrorsStacktrace(err error, context int, appPackagePrefixes []string) *Stacktrace {
stacktracer, errHasStacktrace := err.(interface {
StackTrace() errors.StackTrace
})
if !errHasStacktrace {
return nil
}
var frames []*StacktraceFrame
for _, f := range stacktracer.StackTrace() {
pc := uintptr(f) - 1
fn := runtime.FuncForPC(pc)
var file string
var line int
if fn != nil {
file, line = fn.FileLine(pc)
} else {
file = "unknown"
}
frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
if frame != nil {
frames = append([]*StacktraceFrame{frame}, frames...)
}
}
return &Stacktrace{Frames: frames}
}
func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []string) *Stacktrace {
pkgErrorsStacktrace := recoverPkgErrorsStacktrace(err, context, appPackagePrefixes)
if pkgErrorsStacktrace != nil {
return pkgErrorsStacktrace
} else {
return NewStacktrace(skip+1, context, appPackagePrefixes)
}
}and new CaptureError + CaptureErrorAndWait should be updated in similar manner func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if err == nil {
return ""
}
if client.shouldExcludeErr(err.Error()) {
return ""
}
packet := NewPacketWithExtra(
err.Error(),
extractExtra(err),
append(
append(interfaces, client.context.interfaces()...),
exceptions(err, client.includePaths),
)...,
)
eventID, _ := client.Capture(packet, tags)
return eventID
} |

Following from #155, I realized that always passing in pkg/error's cause into
GetOrNewStacktracedoesn't always work. This PR modifies the behaviour of this function so that it gives us the expected stack trace.For context, the pkg/errors package allows you to create brand new errors using
errors.Newanderrors.Errorfwhich has a stack trace and does not wrap anything. It also allows you to wrap other errors usingerrors.Wrap,Wrapf, andWithStackwhich returns a new error with a stack trace at the pointWrapetc was called, and has the original error as the cause. So if you are using pkg/error in you application, then whenraven.CaptureError(err)is called, you end up in one of the following situations:To provide the most context to the viewer of the error on Sentry, you would want to include the stack trace information as close to the original error as possible. For the scenarios above you want the stack trace at the point where:
The issue that this PR addresses is that naively using
errors.Cause(err)will return the original error which, in the case of (2), won't always contain the stack trace that we want. Instead I added a new private functioncauseWithStacktracewhich finds the innermost error that does contain a stack trace. In the case (1) this returns the original error, in the case (2) this returns the first wrapped error, and in the case (3) this returns nil (since no layers contain a stack trace).CaptureErrorandCaptureErrorAndWaitnow passes the error it receives intoGetOrNewStacktracewhich callscauseWithStacktrace. This PR also includes some go fmt changes and lint fixes.