errortracking/sentry_tracker.go (65 lines of code) (raw):
package errortracking
import (
"reflect"
"github.com/getsentry/sentry-go"
)
// maxErrorDepth is the maximum number of errors reported in a chain of errors.
// This protects the SDK from an arbitrarily long chain of wrapped errors.
//
// An additional consideration is that arguably reporting a long chain of errors
// is of little use when debugging production errors with Sentry. The Sentry UI
// is not optimized for long chains either. The top-level error together with a
// stack trace is often the most useful information.
const maxErrorDepth = 10
type hub interface {
CaptureEvent(event *sentry.Event) *sentry.EventID
}
type sentryTracker struct {
hub hub
}
func newSentryTracker(hub hub) *sentryTracker {
return &sentryTracker{
hub: hub,
}
}
func (s *sentryTracker) Capture(err error, opts ...CaptureOption) {
cfg, event := applyCaptureOptions(opts)
if cfg.attachStackTrace {
attachExceptions(event, err)
} else {
singleException(event, err)
}
s.hub.CaptureEvent(event)
}
func singleException(event *sentry.Event, err error) {
event.Exception = []sentry.Exception{
{
Type: reflect.TypeOf(err).String(),
Value: err.Error(),
Stacktrace: sentry.ExtractStacktrace(err),
},
}
}
// attachExceptions is a modified copy of https://github.com/getsentry/sentry-go/blob/d5877e5a5ddc56d2fd9b48b7a5162a5d7ee47cd8/client.go
// which allows the stack trace with maxErrorDepth to be attached. This allows the event to keep the CaptureOptions
// in place, as using sentry.CaptureException would remove those fields.
func attachExceptions(event *sentry.Event, err error) {
if err == nil {
return
}
for i := 0; i < maxErrorDepth && err != nil; i++ {
event.Exception = append(event.Exception, sentry.Exception{
Value: err.Error(),
Type: reflect.TypeOf(err).String(),
Stacktrace: sentry.ExtractStacktrace(err),
})
switch previous := err.(type) {
case interface{ Unwrap() error }:
err = previous.Unwrap()
case interface{ Cause() error }:
err = previous.Cause()
default:
err = nil
}
}
// Add a trace of the current stack to the most recent error in a chain if
// it doesn't have a stack trace yet.
// We only add to the most recent error to avoid duplication and because the
// current stack is most likely unrelated to errors deeper in the chain.
if event.Exception[0].Stacktrace == nil {
event.Exception[0].Stacktrace = sentry.NewStacktrace()
}
// event.Exception should be sorted such that the most recent error is last.
reverse(event.Exception)
}
// reverse reverses the slice a in place.
func reverse(a []sentry.Exception) {
for i := len(a)/2 - 1; i >= 0; i-- {
opp := len(a) - 1 - i
a[i], a[opp] = a[opp], a[i]
}
}