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  }