From 5caf94e3cc97bbe3c691fb263e6a676a0f7b4246 Mon Sep 17 00:00:00 2001 From: Heng Zhang Date: Mon, 7 Aug 2023 22:47:27 +0800 Subject: [PATCH 1/3] fix: missing stack trace for parsing error in logrusentry (#677) --- logrus/logrusentry.go | 20 +++++++++++++++++--- logrus/logrusentry_test.go | 34 ++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/logrus/logrusentry.go b/logrus/logrusentry.go index 0a3a72216..29297574f 100644 --- a/logrus/logrusentry.go +++ b/logrus/logrusentry.go @@ -4,10 +4,12 @@ package sentrylogrus import ( "errors" "net/http" + "reflect" "time" - sentry "github.com/getsentry/sentry-go" "github.com/sirupsen/logrus" + + sentry "github.com/getsentry/sentry-go" ) // These default log field keys are used to pass specific metadata in a way that @@ -182,17 +184,22 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event { } func (h *Hook) exceptions(err error) []sentry.Exception { + if err == nil { + return nil + } + if !h.hub.Client().Options().AttachStacktrace { return []sentry.Exception{{ - Type: "error", + Type: reflect.TypeOf(err).String(), Value: err.Error(), }} } + excs := []sentry.Exception{} var last *sentry.Exception for ; err != nil; err = errors.Unwrap(err) { exc := sentry.Exception{ - Type: "error", + Type: reflect.TypeOf(err).String(), Value: err.Error(), Stacktrace: sentry.ExtractStacktrace(err), } @@ -208,6 +215,13 @@ func (h *Hook) exceptions(err error) []sentry.Exception { excs = append(excs, exc) last = &excs[len(excs)-1] } + + // Add a trace of the current stack to the most recent error in a chain if + // it doesn't have a stack trace yet. + if excs[0].Stacktrace == nil { + excs[0].Stacktrace = sentry.NewStacktrace() + } + // reverse for i, j := 0, len(excs)-1; i < j; i, j = i+1, j-1 { excs[i], excs[j] = excs[j], excs[i] diff --git a/logrus/logrusentry_test.go b/logrus/logrusentry_test.go index b206d5b85..5a13a2b16 100644 --- a/logrus/logrusentry_test.go +++ b/logrus/logrusentry_test.go @@ -152,7 +152,7 @@ func Test_entryToEvent(t *testing.T) { Level: "fatal", Extra: map[string]interface{}{}, Exception: []sentry.Exception{ - {Type: "error", Value: "things failed"}, + {Type: "*errors.errorString", Value: "things failed", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, }, @@ -181,7 +181,7 @@ func Test_entryToEvent(t *testing.T) { Level: "fatal", Extra: map[string]interface{}{}, Exception: []sentry.Exception{ - {Type: "error", Value: "failure", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + {Type: "*errors.withStack", Value: "failure", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, }, @@ -267,29 +267,35 @@ func Test_exceptions(t *testing.T) { err error want []sentry.Exception }{ + { + name: "error is nil", + trace: true, + err: nil, + want: nil, + }, { name: "std error", trace: true, err: errors.New("foo"), want: []sentry.Exception{ - {Type: "error", Value: "foo"}, + {Type: "*errors.errorString", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, { - name: "wrapped, no stack", + name: "wrapped error", trace: true, err: fmt.Errorf("foo: %w", errors.New("bar")), want: []sentry.Exception{ - {Type: "error", Value: "bar"}, - {Type: "error", Value: "foo: bar"}, + {Type: "*errors.errorString", Value: "bar"}, + {Type: "*fmt.wrapError", Value: "foo: bar", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, { - name: "ignored stack", + name: "missing stack for pkgerr", trace: false, err: pkgerr.New("foo"), want: []sentry.Exception{ - {Type: "error", Value: "foo"}, + {Type: "*errors.fundamental", Value: "foo"}, }, }, { @@ -297,7 +303,7 @@ func Test_exceptions(t *testing.T) { trace: true, err: pkgerr.New("foo"), want: []sentry.Exception{ - {Type: "error", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + {Type: "*errors.fundamental", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, { @@ -311,11 +317,11 @@ func Test_exceptions(t *testing.T) { return fmt.Errorf("wrapped: %w", err) }(), want: []sentry.Exception{ - {Type: "error", Value: "original"}, - {Type: "error", Value: "fmt: original"}, - {Type: "error", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, - {Type: "error", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, - {Type: "error", Value: "wrapped: wrap: fmt: original"}, + {Type: "*errors.errorString", Value: "original"}, + {Type: "*fmt.wrapError", Value: "fmt: original"}, + {Type: "*errors.withStack", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + {Type: "*errors.withStack", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + {Type: "*fmt.wrapError", Value: "wrapped: wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, } From a91768d210a8415cb972156eec1981410072bbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sun, 17 Mar 2024 18:35:34 +0100 Subject: [PATCH 2/3] add changes from RFC 0079 (parent_id, exception_id, is_exception_group) --- logrus/logrusentry.go | 50 +++++--- logrus/logrusentry_test.go | 245 +++++++++++++++++++++++++------------ 2 files changed, 202 insertions(+), 93 deletions(-) diff --git a/logrus/logrusentry.go b/logrus/logrusentry.go index f82d999ee..c4f733a15 100644 --- a/logrus/logrusentry.go +++ b/logrus/logrusentry.go @@ -201,25 +201,25 @@ func (h *Hook) exceptions(err error) []sentry.Exception { }} } - excs := []sentry.Exception{} - var last *sentry.Exception - for ; err != nil; err = errors.Unwrap(err) { - exc := sentry.Exception{ - Type: reflect.TypeOf(err).String(), + var excs []sentry.Exception + for err != nil { + // Add the current error to the exception slice with its details + excs = append(excs, sentry.Exception{ Value: err.Error(), + Type: reflect.TypeOf(err).String(), Stacktrace: sentry.ExtractStacktrace(err), + }) + + // Attempt to unwrap the error using the standard library's Unwrap method. + // If errors.Unwrap returns nil, it means either there is no error to unwrap, + // or the error does not implement the Unwrap method. + unwrappedErr := errors.Unwrap(err) + + if unwrappedErr == nil { + break } - if last != nil && exc.Value == last.Value { - if last.Stacktrace == nil { - last.Stacktrace = exc.Stacktrace - continue - } - if exc.Stacktrace == nil { - continue - } - } - excs = append(excs, exc) - last = &excs[len(excs)-1] + + err = unwrappedErr } // Add a trace of the current stack to the most recent error in a chain if @@ -228,10 +228,28 @@ func (h *Hook) exceptions(err error) []sentry.Exception { excs[0].Stacktrace = sentry.NewStacktrace() } + if len(excs) <= 1 { + return excs + } + // reverse for i, j := 0, len(excs)-1; i < j; i, j = i+1, j-1 { excs[i], excs[j] = excs[j], excs[i] } + + for i := range excs { + excs[i].Mechanism = &sentry.Mechanism{ + Data: map[string]any{ + "is_exception_group": true, + "exception_id": i, + }, + } + if i == 0 { + continue + } + excs[i].Mechanism.Data["parent_id"] = i - 1 + } + return excs } diff --git a/logrus/logrusentry_test.go b/logrus/logrusentry_test.go index afddc46ba..0ff8ac583 100644 --- a/logrus/logrusentry_test.go +++ b/logrus/logrusentry_test.go @@ -67,74 +67,67 @@ func TestFire(t *testing.T) { func Test_entryToEvent(t *testing.T) { t.Parallel() - tests := []struct { - name string + tests := map[string]struct { entry *logrus.Entry want *sentry.Event }{ - { - name: "empty entry", + "empty entry": { entry: &logrus.Entry{}, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, }, }, - { - name: "data fields", + "data fields": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ "foo": 123.4, "bar": "oink", }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{"bar": "oink", "foo": 123.4}, + Extra: map[string]any{"bar": "oink", "foo": 123.4}, }, }, - { - name: "info level", + "info level": { entry: &logrus.Entry{ Level: logrus.InfoLevel, }, want: &sentry.Event{ Level: "info", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, }, }, - { - name: "message", + "message": { entry: &logrus.Entry{ Message: "the only thing we have to fear is fear itself", }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, Message: "the only thing we have to fear is fear itself", }, }, - { - name: "timestamp", + "timestamp": { entry: &logrus.Entry{ Time: time.Unix(1, 2).UTC(), }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, Timestamp: time.Unix(1, 2).UTC(), }, }, - { - name: "http request", + "http request": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ FieldRequest: httptest.NewRequest("GET", "/", nil), }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, Request: &sentry.Request{ URL: "http://example.com/", Method: http.MethodGet, @@ -142,54 +135,73 @@ func Test_entryToEvent(t *testing.T) { }, }, }, - { - name: "error", + "error": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ logrus.ErrorKey: errors.New("things failed"), }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, Exception: []sentry.Exception{ {Type: "*errors.errorString", Value: "things failed", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, }, - { - name: "non-error", + "non-error": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ logrus.ErrorKey: "this isn't really an error", }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{ + Extra: map[string]any{ "error": "this isn't really an error", }, }, }, - { - name: "error with stack trace", + "error with stack trace": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ logrus.ErrorKey: pkgerr.WithStack(errors.New("failure")), }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, Exception: []sentry.Exception{ - {Type: "*errors.withStack", Value: "failure", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + { + Type: "*errors.errorString", + Value: "failure", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 0, + "is_exception_group": true, + }, + }, + }, + { + Type: "*errors.withStack", + Value: "failure", + Stacktrace: &sentry.Stacktrace{ + Frames: []sentry.Frame{}, + }, + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 1, + "is_exception_group": true, + "parent_id": 0, + }, + }, + }, }, }, }, - { - name: "user", + "user": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ FieldUser: sentry.User{ ID: "bob", }, @@ -197,16 +209,15 @@ func Test_entryToEvent(t *testing.T) { }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, User: sentry.User{ ID: "bob", }, }, }, - { - name: "user pointer", + "user pointer": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ FieldUser: &sentry.User{ ID: "alice", }, @@ -214,22 +225,21 @@ func Test_entryToEvent(t *testing.T) { }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{}, + Extra: map[string]any{}, User: sentry.User{ ID: "alice", }, }, }, - { - name: "non-user", + "non-user": { entry: &logrus.Entry{ - Data: map[string]interface{}{ + Data: map[string]any{ FieldUser: "just say no to drugs", }, }, want: &sentry.Event{ Level: "fatal", - Extra: map[string]interface{}{ + Extra: map[string]any{ "user": "just say no to drugs", }, }, @@ -243,15 +253,13 @@ func Test_entryToEvent(t *testing.T) { t.Fatal(err) } - for _, tt := range tests { + for name, tt := range tests { tt := tt - t.Run(tt.name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := h.entryToEvent(tt.entry) opts := cmp.Options{ - cmpopts.IgnoreFields(sentry.Event{}, - "sdkMetaData", - ), + cmpopts.IgnoreFields(sentry.Event{}, "sdkMetaData"), } if d := cmp.Diff(tt.want, got, opts); d != "" { t.Error(d) @@ -262,53 +270,72 @@ func Test_entryToEvent(t *testing.T) { func Test_exceptions(t *testing.T) { t.Parallel() - tests := []struct { - name string + tests := map[string]struct { trace bool err error want []sentry.Exception }{ - { - name: "error is nil", + "error is nil": { trace: true, err: nil, want: nil, }, - { - name: "std error", + "std error": { trace: true, err: errors.New("foo"), want: []sentry.Exception{ - {Type: "*errors.errorString", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + { + Type: "*errors.errorString", + Value: "foo", + Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, + }, }, }, - { - name: "wrapped error", + "wrapped error": { trace: true, err: fmt.Errorf("foo: %w", errors.New("bar")), want: []sentry.Exception{ - {Type: "*errors.errorString", Value: "bar"}, - {Type: "*fmt.wrapError", Value: "foo: bar", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + { + Type: "*errors.errorString", + Value: "bar", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 0, + "is_exception_group": true, + }, + }, + }, + { + Type: "*fmt.wrapError", + Value: "foo: bar", + Stacktrace: &sentry.Stacktrace{ + Frames: []sentry.Frame{}, + }, + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 1, + "is_exception_group": true, + "parent_id": 0, + }, + }, + }, }, }, - { - name: "missing stack for pkgerr", + "missing stack for pkgerr": { trace: false, err: pkgerr.New("foo"), want: []sentry.Exception{ {Type: "*errors.fundamental", Value: "foo"}, }, }, - { - name: "stack", + "stack": { trace: true, err: pkgerr.New("foo"), want: []sentry.Exception{ {Type: "*errors.fundamental", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, }, }, - { - name: "multi-wrapped error", + "multi-wrapped error": { trace: true, err: func() error { err := errors.New("original") @@ -318,25 +345,89 @@ func Test_exceptions(t *testing.T) { return fmt.Errorf("wrapped: %w", err) }(), want: []sentry.Exception{ - {Type: "*errors.errorString", Value: "original"}, - {Type: "*fmt.wrapError", Value: "fmt: original"}, - {Type: "*errors.withStack", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, - {Type: "*errors.withStack", Value: "wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, - {Type: "*fmt.wrapError", Value: "wrapped: wrap: fmt: original", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, + { + Type: "*errors.errorString", + Value: "original", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 0, + "is_exception_group": true, + }, + }, + }, + { + Type: "*fmt.wrapError", + Value: "fmt: original", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 1, + "is_exception_group": true, + "parent_id": 0, + }, + }, + }, + { + Type: "*errors.withMessage", + Value: "wrap: fmt: original", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 2, + "is_exception_group": true, + "parent_id": 1, + }, + }, + }, + { + Type: "*errors.withStack", + Value: "wrap: fmt: original", + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 3, + "is_exception_group": true, + "parent_id": 2, + }, + }, + Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, + }, + { + Type: "*errors.withStack", + Value: "wrap: fmt: original", + Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 4, + "is_exception_group": true, + "parent_id": 3, + }, + }, + }, + { + Type: "*fmt.wrapError", + Value: "wrapped: wrap: fmt: original", + Stacktrace: &sentry.Stacktrace{ + Frames: []sentry.Frame{}, + }, + Mechanism: &sentry.Mechanism{ + Data: map[string]any{ + "exception_id": 5, + "is_exception_group": true, + "parent_id": 4, + }, + }, + }, }, }, } - for _, tt := range tests { + for name, tt := range tests { tt := tt - t.Run(tt.name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() h, err := New(nil, sentry.ClientOptions{AttachStacktrace: tt.trace}) if err != nil { t.Fatal(err) } got := h.exceptions(tt.err) - if d := cmp.Diff(tt.want, got); d != "" { t.Error(d) } From c03711a4d75563cc476b83685fe174008266ba28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Tue, 26 Mar 2024 18:54:39 +0100 Subject: [PATCH 3/3] use SetException in logrus --- CHANGELOG.md | 4 + interfaces.go | 5 +- logrus/logrusentry.go | 68 +-------------- logrus/logrusentry_test.go | 168 ------------------------------------- 4 files changed, 8 insertions(+), 237 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d27a4e6..5951037e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - Add `Fiber` integration ([#795](https://github.com/getsentry/sentry-go/pull/795)) - Use `errors.Unwrap()` to create exception groups ([#792](https://github.com/getsentry/sentry-go/pull/792)) +### Fixes + +- Fix missing stack trace for parsing error in logrusentry ([#689](https://github.com/getsentry/sentry-go/pull/689)) + ## 0.27.0 The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.27.0. diff --git a/interfaces.go b/interfaces.go index d57f753a8..689c11578 100644 --- a/interfaces.go +++ b/interfaces.go @@ -341,7 +341,8 @@ type Event struct { // 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. +// into while unwrapping the errors. If maxErrorDepth is -1, we will +// unwrap all errors in the chain. func (e *Event) SetException(exception error, maxErrorDepth int) { if exception == nil { return @@ -349,7 +350,7 @@ func (e *Event) SetException(exception error, maxErrorDepth int) { err := exception - for i := 0; err != nil && i < maxErrorDepth; i++ { + for i := 0; err != nil && (i < maxErrorDepth || maxErrorDepth == -1); i++ { // Add the current error to the exception slice with its details e.Exception = append(e.Exception, Exception{ Value: err.Error(), diff --git a/logrus/logrusentry.go b/logrus/logrusentry.go index c4f733a15..f9d921651 100644 --- a/logrus/logrusentry.go +++ b/logrus/logrusentry.go @@ -4,7 +4,6 @@ package sentrylogrus import ( "errors" "net/http" - "reflect" "time" "github.com/sirupsen/logrus" @@ -162,8 +161,7 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event { } if err, ok := s.Extra[logrus.ErrorKey].(error); ok { delete(s.Extra, logrus.ErrorKey) - ex := h.exceptions(err) - s.Exception = ex + s.SetException(err, -1) } key = h.key(FieldUser) if user, ok := s.Extra[key].(sentry.User); ok { @@ -189,70 +187,6 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event { return s } -func (h *Hook) exceptions(err error) []sentry.Exception { - if err == nil { - return nil - } - - if !h.hub.Client().Options().AttachStacktrace { - return []sentry.Exception{{ - Type: reflect.TypeOf(err).String(), - Value: err.Error(), - }} - } - - var excs []sentry.Exception - for err != nil { - // Add the current error to the exception slice with its details - excs = append(excs, sentry.Exception{ - Value: err.Error(), - Type: reflect.TypeOf(err).String(), - Stacktrace: sentry.ExtractStacktrace(err), - }) - - // Attempt to unwrap the error using the standard library's Unwrap method. - // If errors.Unwrap returns nil, it means either there is no error to unwrap, - // or the error does not implement the Unwrap method. - unwrappedErr := errors.Unwrap(err) - - if unwrappedErr == nil { - break - } - - err = unwrappedErr - } - - // Add a trace of the current stack to the most recent error in a chain if - // it doesn't have a stack trace yet. - if excs[0].Stacktrace == nil { - excs[0].Stacktrace = sentry.NewStacktrace() - } - - if len(excs) <= 1 { - return excs - } - - // reverse - for i, j := 0, len(excs)-1; i < j; i, j = i+1, j-1 { - excs[i], excs[j] = excs[j], excs[i] - } - - for i := range excs { - excs[i].Mechanism = &sentry.Mechanism{ - Data: map[string]any{ - "is_exception_group": true, - "exception_id": i, - }, - } - if i == 0 { - continue - } - excs[i].Mechanism.Data["parent_id"] = i - 1 - } - - return excs -} - // Flush waits until the underlying Sentry transport sends any buffered events, // blocking for at most the given timeout. It returns false if the timeout was // reached, in which case some events may not have been sent. diff --git a/logrus/logrusentry_test.go b/logrus/logrusentry_test.go index 0ff8ac583..1dbe6f47c 100644 --- a/logrus/logrusentry_test.go +++ b/logrus/logrusentry_test.go @@ -2,7 +2,6 @@ package sentrylogrus import ( "errors" - "fmt" "net/http" "net/http/httptest" "strings" @@ -267,170 +266,3 @@ func Test_entryToEvent(t *testing.T) { }) } } - -func Test_exceptions(t *testing.T) { - t.Parallel() - tests := map[string]struct { - trace bool - err error - want []sentry.Exception - }{ - "error is nil": { - trace: true, - err: nil, - want: nil, - }, - "std error": { - trace: true, - err: errors.New("foo"), - want: []sentry.Exception{ - { - Type: "*errors.errorString", - Value: "foo", - Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, - }, - }, - }, - "wrapped error": { - trace: true, - err: fmt.Errorf("foo: %w", errors.New("bar")), - want: []sentry.Exception{ - { - Type: "*errors.errorString", - Value: "bar", - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 0, - "is_exception_group": true, - }, - }, - }, - { - Type: "*fmt.wrapError", - Value: "foo: bar", - Stacktrace: &sentry.Stacktrace{ - Frames: []sentry.Frame{}, - }, - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 1, - "is_exception_group": true, - "parent_id": 0, - }, - }, - }, - }, - }, - "missing stack for pkgerr": { - trace: false, - err: pkgerr.New("foo"), - want: []sentry.Exception{ - {Type: "*errors.fundamental", Value: "foo"}, - }, - }, - "stack": { - trace: true, - err: pkgerr.New("foo"), - want: []sentry.Exception{ - {Type: "*errors.fundamental", Value: "foo", Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}}, - }, - }, - "multi-wrapped error": { - trace: true, - err: func() error { - err := errors.New("original") - err = fmt.Errorf("fmt: %w", err) - err = pkgerr.Wrap(err, "wrap") - err = pkgerr.WithStack(err) - return fmt.Errorf("wrapped: %w", err) - }(), - want: []sentry.Exception{ - { - Type: "*errors.errorString", - Value: "original", - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 0, - "is_exception_group": true, - }, - }, - }, - { - Type: "*fmt.wrapError", - Value: "fmt: original", - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 1, - "is_exception_group": true, - "parent_id": 0, - }, - }, - }, - { - Type: "*errors.withMessage", - Value: "wrap: fmt: original", - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 2, - "is_exception_group": true, - "parent_id": 1, - }, - }, - }, - { - Type: "*errors.withStack", - Value: "wrap: fmt: original", - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 3, - "is_exception_group": true, - "parent_id": 2, - }, - }, - Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, - }, - { - Type: "*errors.withStack", - Value: "wrap: fmt: original", - Stacktrace: &sentry.Stacktrace{Frames: []sentry.Frame{}}, - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 4, - "is_exception_group": true, - "parent_id": 3, - }, - }, - }, - { - Type: "*fmt.wrapError", - Value: "wrapped: wrap: fmt: original", - Stacktrace: &sentry.Stacktrace{ - Frames: []sentry.Frame{}, - }, - Mechanism: &sentry.Mechanism{ - Data: map[string]any{ - "exception_id": 5, - "is_exception_group": true, - "parent_id": 4, - }, - }, - }, - }, - }, - } - - for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { - t.Parallel() - h, err := New(nil, sentry.ClientOptions{AttachStacktrace: tt.trace}) - if err != nil { - t.Fatal(err) - } - got := h.exceptions(tt.err) - if d := cmp.Diff(tt.want, got); d != "" { - t.Error(d) - } - }) - } -}