From eb7fca983236c965a6b0bba9ee225a9f95f40be2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 2 May 2023 11:44:33 +0200 Subject: [PATCH] feat: Export client.EventFromMessage, client.EventFromException, and event.SetException (#607) Co-authored-by: Kevin Snyder Co-authored-by: Anton Ovchinnikov --- client.go | 52 ++++++++++++++------------------------------------- interfaces.go | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/client.go b/client.go index 49e2cd5ff..c20159568 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,6 @@ import ( "math/rand" "net/http" "os" - "reflect" "sort" "strings" "sync" @@ -378,13 +377,13 @@ func (client Client) Options() ClientOptions { // CaptureMessage captures an arbitrary message. func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID { - event := client.eventFromMessage(message, LevelInfo) + event := client.EventFromMessage(message, LevelInfo) return client.CaptureEvent(event, hint, scope) } // CaptureException captures an error. func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID { - event := client.eventFromException(exception, LevelError) + event := client.EventFromException(exception, LevelError) return client.CaptureEvent(event, hint, scope) } @@ -439,11 +438,11 @@ func (client *Client) RecoverWithContext( var event *Event switch err := err.(type) { case error: - event = client.eventFromException(err, LevelFatal) + event = client.EventFromException(err, LevelFatal) case string: - event = client.eventFromMessage(err, LevelFatal) + event = client.EventFromMessage(err, LevelFatal) default: - event = client.eventFromMessage(fmt.Sprintf("%#v", err), LevelFatal) + event = client.EventFromMessage(fmt.Sprintf("%#v", err), LevelFatal) } return client.CaptureEvent(event, hint, scope) } @@ -463,10 +462,11 @@ func (client *Client) Flush(timeout time.Duration) bool { return client.Transport.Flush(timeout) } -func (client *Client) eventFromMessage(message string, level Level) *Event { +// EventFromMessage creates an event from the given message string. +func (client *Client) EventFromMessage(message string, level Level) *Event { if message == "" { err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())} - return client.eventFromException(err, level) + return client.EventFromException(err, level) } event := NewEvent() event.Level = level @@ -483,41 +483,17 @@ func (client *Client) eventFromMessage(message string, level Level) *Event { return event } -func (client *Client) eventFromException(exception error, level Level) *Event { - err := exception - if err == nil { - err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())} - } - +// EventFromException creates a new Sentry event from the given `error` instance. +func (client *Client) EventFromException(exception error, level Level) *Event { event := NewEvent() event.Level = level - for i := 0; i < client.options.MaxErrorDepth && err != nil; i++ { - event.Exception = append(event.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{ Cause() error }: - err = previous.Cause() - default: - err = nil - } - } - - // 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 event.Exception[0].Stacktrace == nil { - event.Exception[0].Stacktrace = NewStacktrace() + err := exception + if err == nil { + err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())} } - // event.Exception should be sorted such that the most recent error is last. - reverse(event.Exception) + event.SetException(err, client.options.MaxErrorDepth) return event } diff --git a/interfaces.go b/interfaces.go index 91c5e476e..ad1cec04d 100644 --- a/interfaces.go +++ b/interfaces.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "net/http" + "reflect" "strings" "time" ) @@ -313,6 +314,44 @@ type Event struct { sdkMetaData SDKMetaData } +// 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 + } + + 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{ Cause() error }: + err = previous.Cause() + default: + err = nil + } + } + + // 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) +} + // TODO: Event.Contexts map[string]interface{} => map[string]EventContext, // to prevent accidentally storing T when we mean *T. // For example, the TraceContext must be stored as *TraceContext to pick up the