From f146e2caf9c3f643629b570e3ecf3a83cde783a5 Mon Sep 17 00:00:00 2001 From: Sergey Petrov Date: Wed, 1 Feb 2023 05:02:48 +0400 Subject: [PATCH] Add special handling for *fmt.wrapError to make events in Sentry more useful --- sentry_core.go | 70 +++++++++++++++++++++++++++++---------------- sentry_core_test.go | 22 +++++++++++++- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/sentry_core.go b/sentry_core.go index 23ac906..c872834 100644 --- a/sentry_core.go +++ b/sentry_core.go @@ -1,6 +1,7 @@ package logger import ( + "fmt" "reflect" "time" @@ -125,39 +126,23 @@ func (s *SentryCore) captureEvent(ent zapcore.Entry, data *zapcore.MapObjectEnco event.Message = ent.Message event.Extra = data.Fields - for i := 0; i < 10 && errField != nil; i++ { - event.Exception = append(event.Exception, sentry.Exception{ - Value: errField.Error(), - Type: reflect.TypeOf(errField).String(), - Stacktrace: extractStacktrace(errField), - }) - switch wrapped := errField.(type) { //nolint:errorlint - case interface{ Unwrap() error }: - errField = wrapped.Unwrap() - case interface{ Cause() error }: - errField = wrapped.Cause() - default: - errField = nil - } + if errField != nil { + event.Exception = s.convertErrorToException(errField) } + event.Threads = []sentry.Thread{{ + ID: "current", + Current: true, + Crashed: ent.Level >= zapcore.DPanicLevel, + }} + if len(event.Exception) != 0 { if event.Exception[0].Stacktrace == nil { event.Exception[0].Stacktrace = newStacktrace() } event.Exception[0].ThreadID = "current" - event.Threads = []sentry.Thread{{ - ID: "current", - Current: true, - Crashed: ent.Level >= zapcore.DPanicLevel, - }} } else { - event.Threads = []sentry.Thread{{ - ID: "current", - Stacktrace: newStacktrace(), - Current: true, - Crashed: ent.Level >= zapcore.DPanicLevel, - }} + event.Threads[0].Stacktrace = newStacktrace() } // event.Exception should be sorted such that the most recent error is last @@ -169,6 +154,41 @@ func (s *SentryCore) captureEvent(ent zapcore.Entry, data *zapcore.MapObjectEnco s.hub.CaptureEvent(event) } +func (s *SentryCore) convertErrorToException(errValue error) []sentry.Exception { + exceptions := make([]sentry.Exception, 0) + firstMeaningfulError := -1 + for i := 0; i < 10 && errValue != nil; i++ { + errorType := reflect.TypeOf(errValue).String() + exceptions = append(exceptions, sentry.Exception{ + Value: errValue.Error(), + Type: errorType, + Stacktrace: extractStacktrace(errValue), + }) + + if errorType != "*fmt.wrapError" && firstMeaningfulError == -1 { + firstMeaningfulError = i + } + + switch wrapped := errValue.(type) { //nolint:errorlint + case interface{ Unwrap() error }: + errValue = wrapped.Unwrap() + case interface{ Cause() error }: + errValue = wrapped.Cause() + default: + errValue = nil + } + } + + // If the first errors are wrapped errors, we want to show actual error type instead of *fmt.wrapError + if firstMeaningfulError != -1 { + for i := 0; i < firstMeaningfulError; i++ { + exceptions[i].Type = fmt.Sprintf("wrapped<%s>", exceptions[firstMeaningfulError].Type) + } + } + + return exceptions +} + func (s *SentryCore) Sync() error { s.hub.Flush(30 * time.Second) return nil diff --git a/sentry_core_test.go b/sentry_core_test.go index f2499e6..5f1aa1c 100644 --- a/sentry_core_test.go +++ b/sentry_core_test.go @@ -229,7 +229,7 @@ func (suite *SentryCoreSuite) TestWriteChainedErrors() { suite.Equal("simple error", event.Exception[1].Value) suite.NotNil(event.Exception[1].Stacktrace) - suite.Equal("*fmt.wrapError", event.Exception[2].Type) + suite.Equal("wrapped<*errors.withStack>", event.Exception[2].Type) suite.Equal("wrap with fmt.Errorf: simple error", event.Exception[2].Value) suite.NotNil(event.Exception[2].Stacktrace) @@ -248,6 +248,26 @@ func (suite *SentryCoreSuite) TestWriteChainedErrors() { logger.Error("message with chained error", zap.Error(err)) } +func (suite *SentryCoreSuite) TestStrippingWrappedErrors() { + core := NewSentryCore(suite.hub).(*SentryCore) + + err := stderrors.New("simple error") + err = fmt.Errorf("first wrap: %w", err) + err = fmt.Errorf("second wrap: %w", err) + + exceptions := core.convertErrorToException(err) + suite.Len(exceptions, 3) + + suite.Equal("wrapped<*errors.errorString>", exceptions[0].Type) + suite.Equal("second wrap: first wrap: simple error", exceptions[0].Value) + + suite.Equal("wrapped<*errors.errorString>", exceptions[1].Type) + suite.Equal("first wrap: simple error", exceptions[1].Value) + + suite.Equal("*errors.errorString", exceptions[2].Type) + suite.Equal("simple error", exceptions[2].Value) +} + func TestSentryCore(t *testing.T) { suite.Run(t, new(SentryCoreSuite)) }