gitlab.com/gitlab-org/labkit@v1.21.0/errortracking/sentry_tracker.go (about) 1 package errortracking 2 3 import ( 4 "reflect" 5 6 "github.com/getsentry/sentry-go" 7 ) 8 9 // maxErrorDepth is the maximum number of errors reported in a chain of errors. 10 // This protects the SDK from an arbitrarily long chain of wrapped errors. 11 // 12 // An additional consideration is that arguably reporting a long chain of errors 13 // is of little use when debugging production errors with Sentry. The Sentry UI 14 // is not optimized for long chains either. The top-level error together with a 15 // stack trace is often the most useful information. 16 const maxErrorDepth = 10 17 18 type hub interface { 19 CaptureEvent(event *sentry.Event) *sentry.EventID 20 } 21 22 type sentryTracker struct { 23 hub hub 24 } 25 26 func newSentryTracker(hub hub) *sentryTracker { 27 return &sentryTracker{ 28 hub: hub, 29 } 30 } 31 32 func (s *sentryTracker) Capture(err error, opts ...CaptureOption) { 33 cfg, event := applyCaptureOptions(opts) 34 35 if cfg.attachStackTrace { 36 attachExceptions(event, err) 37 } else { 38 singleException(event, err) 39 } 40 41 s.hub.CaptureEvent(event) 42 } 43 44 func singleException(event *sentry.Event, err error) { 45 event.Exception = []sentry.Exception{ 46 { 47 Type: reflect.TypeOf(err).String(), 48 Value: err.Error(), 49 Stacktrace: sentry.ExtractStacktrace(err), 50 }, 51 } 52 } 53 54 // attachExceptions is a modified copy of https://github.com/getsentry/sentry-go/blob/d5877e5a5ddc56d2fd9b48b7a5162a5d7ee47cd8/client.go 55 // which allows the stack trace with maxErrorDepth to be attached. This allows the event to keep the CaptureOptions 56 // in place, as using sentry.CaptureException would remove those fields. 57 func attachExceptions(event *sentry.Event, err error) { 58 if err == nil { 59 return 60 } 61 62 for i := 0; i < maxErrorDepth && err != nil; i++ { 63 event.Exception = append(event.Exception, sentry.Exception{ 64 Value: err.Error(), 65 Type: reflect.TypeOf(err).String(), 66 Stacktrace: sentry.ExtractStacktrace(err), 67 }) 68 switch previous := err.(type) { 69 case interface{ Unwrap() error }: 70 err = previous.Unwrap() 71 case interface{ Cause() error }: 72 err = previous.Cause() 73 default: 74 err = nil 75 } 76 } 77 78 // Add a trace of the current stack to the most recent error in a chain if 79 // it doesn't have a stack trace yet. 80 // We only add to the most recent error to avoid duplication and because the 81 // current stack is most likely unrelated to errors deeper in the chain. 82 if event.Exception[0].Stacktrace == nil { 83 event.Exception[0].Stacktrace = sentry.NewStacktrace() 84 } 85 86 // event.Exception should be sorted such that the most recent error is last. 87 reverse(event.Exception) 88 } 89 90 // reverse reverses the slice a in place. 91 func reverse(a []sentry.Exception) { 92 for i := len(a)/2 - 1; i >= 0; i-- { 93 opp := len(a) - 1 - i 94 a[i], a[opp] = a[opp], a[i] 95 } 96 }