sentry-go icon indicating copy to clipboard operation
sentry-go copied to clipboard

Support Go 1.20 Unwrap() with a slice of errors

Open ainar-g opened this issue 1 year ago • 11 comments

Summary

Go 1.20 added another way to unwrap errors, interface { Unwrap() []error }. As far as I can see, as of now Event.SetException doesn't support it.

https://github.com/getsentry/sentry-go/blob/cda691d09673fe847351b41adb4c65012cb6a5a9/interfaces.go#L356-L363

Motivation

Developers are updating to Go 1.20, as Go 1.19 is EOL now, and they will expect sentry-go to support the new interface and include all errors into the event data.

Additional Context

ainar-g avatar Aug 08 '23 16:08 ainar-g

Thanks, makes sense, added to the backlog.

tonyo avatar Aug 16 '23 09:08 tonyo

What should we do ? Get the first error in the slice ? Or explore the error's tree recursively (but the exceptions are a simple slice)

pierrre avatar Sep 25 '23 12:09 pierrre

The Python API seems to already have a handler for a similar mechanism called ExceptionGroup:

https://github.com/getsentry/sentry-python/blob/641822dcf3cc90ee0c3e9726d4a5a979d4755c10/sentry_sdk/utils.py#L815-L832

Perhaps the same flatting algorithm could be used?

ainar-g avatar Sep 25 '23 12:09 ainar-g

I'm not very familiar with Python, sorry :sweat_smile: Is it adding recursively all errors to the array of exception ?

pierrre avatar Sep 25 '23 13:09 pierrre

Neither do I really, heh. My point was more that there are similar idioms in other languages, and when the Sentry team starts working on the issue, they should probably follow the same principles as the APIs in those languages do.

And yes, it seems to add them one after the other, at least based on what I can read.

ainar-g avatar Sep 25 '23 13:09 ainar-g

This ?

// SetException appends the unwrapped errors to the event's exception list.
//
// maxErrorDepth is the maximum depth of the error chain we will look
// into while unwrapping the errors.
func (e *Event) SetException(exception error, maxErrorDepth int) {
	err := exception
	if err == nil {
		return
	}

	e.setException(err, maxErrorDepth)

	// 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 e.Exception[0].Stacktrace == nil {
		e.Exception[0].Stacktrace = NewStacktrace()
	}

	// event.Exception should be sorted such that the most recent error is last.
	reverse(e.Exception)
}

func (e *Event) setException(err error, maxErrorDepth int) {
	for i := 0; i < maxErrorDepth && err != nil; i++ {
		e.Exception = append(e.Exception, Exception{
			Value:      err.Error(),
			Type:       reflect.TypeOf(err).String(),
			Stacktrace: ExtractStacktrace(err),
		})
		switch previous := err.(type) {
		case interface{ Unwrap() error }:
			err = previous.Unwrap()
		case interface{ Unwrap() []error }:
			errs := previous.Unwrap()
			for _, errw := range errs {
				e.setException(errw, maxErrorDepth-i-1)
			}
			err = nil
		case interface{ Cause() error }:
			err = previous.Cause()
		default:
			err = nil
		}
	}
}

pierrre avatar Sep 25 '23 15:09 pierrre

Seems like supporting these kind of error groups is a newish feature, see: https://github.com/getsentry/sentry/issues/37716

greywolve avatar Sep 28 '23 01:09 greywolve

Also the RFC: https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md

greywolve avatar Sep 28 '23 15:09 greywolve

Here's the sentry-javascript implementation, so this change will be a bit more involved.

greywolve avatar Sep 28 '23 15:09 greywolve

hello! any progress? Also joinError does not get proper stack trace too.

proost avatar Dec 29 '23 08:12 proost

No progress, still in our backlog.

cleptric avatar Dec 29 '23 09:12 cleptric