From 862cdc18fcd32ddbc71fa1c1b91196509cfe8bcb Mon Sep 17 00:00:00 2001 From: hugoShaka Date: Thu, 28 Nov 2024 16:03:04 -0500 Subject: [PATCH 1/2] Add CompactUserMessage() and StackTraceReport() --- trace.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++ trace_test.go | 21 ++++++++++++ 2 files changed, 111 insertions(+) diff --git a/trace.go b/trace.go index d2a4956..3f85978 100644 --- a/trace.go +++ b/trace.go @@ -88,6 +88,21 @@ type UserMessager interface { UserMessage() string } +// CompactUserMessager returns a user message associated with the error +type CompactUserMessager interface { + // CompactUserMessage formats the wraoped error message as a compact go-style + // wrapped error message. This format is more user-friendly and contains no + // new line nor tab (except the ones already present in the wrapped error). + // For example: + // + // trace.Wrap(trace.Wrap(trace.Errorf("foo"), "bar"), "baz") + // + // Will produce the following compact error: + // + // foo: bar: baz + CompactUserMessage() string +} + // ErrorWrapper wraps another error type ErrorWrapper interface { // OrigError returns the wrapped error @@ -100,6 +115,11 @@ type DebugReporter interface { DebugReport() string } +// StackTraceReporter returns the error stacktrace. +type StackTraceReporter interface { + StackTraceReport() Traces +} + // UserMessage returns user-friendly part of the error func UserMessage(err error) string { if err == nil { @@ -111,6 +131,26 @@ func UserMessage(err error) string { return err.Error() } +// CompactUserMessage formats the wraoped error message as a compact go-style +// wrapped error message. This format is more user-friendly and contains no +// new line nor tab (except the ones already present in the wrapped error). +// For example: +// +// trace.Wrap(trace.Wrap(trace.Errorf("foo"), "bar"), "baz") +// +// Will produce the following compact error: +// +// foo: bar: baz +func CompactUserMessage(err error) string { + if err == nil { + return "" + } + if wrap, ok := err.(CompactUserMessager); ok { + return wrap.CompactUserMessage() + } + return err.Error() +} + // UserMessageWithFields returns user-friendly error with key-pairs as part of the message func UserMessageWithFields(err error) string { if err == nil { @@ -172,6 +212,17 @@ func GetFields(err error) map[string]interface{} { return map[string]interface{}{} } +// StackTraceReport returns the error stacktrace if it was captured. +func StackTraceReport(err error) Traces { + if err == nil { + return Traces{} + } + if wrap, ok := err.(StackTraceReporter); ok { + return wrap.StackTraceReport() + } + return Traces{} +} + // WrapWithMessage wraps the original error into Error and adds user message if any func WrapWithMessage(err error, message interface{}, args ...interface{}) Error { var trace Error @@ -374,6 +425,43 @@ func (e *TraceErr) GoString() string { return e.DebugReport() } +// CompactUserMessage formats the wraoped error message as a compact go-style +// wrapped error message. This format is more user-friendly and contains no +// new line nor tab (except the ones already present in the wrapped error). +// For example: +// +// trace.Wrap(trace.Wrap(trace.Errorf("foo"), "bar"), "baz") +// +// Will produce the following compact error: +// +// foo: bar: baz +func (e *TraceErr) CompactUserMessage() string { + if len(e.Messages) > 0 { + sb := strings.Builder{} + for i := len(e.Messages) - 1; i >= 0; i-- { + sb.WriteString(e.Messages[i]) + sb.WriteString(": ") + } + sb.WriteString(e.Err.Error()) + return sb.String() + } + if e.Message != "" { + // For backwards compatibility return the old user message if it's present. + return e.Message + } + return CompactUserMessage(e.Err) +} + +// StackTraceReport returns the error stacktrace. +func (e *TraceErr) StackTraceReport() Traces { + if len(e.Traces) > 0 { + traces := make(Traces, len(e.Traces)) + copy(traces, e.Traces) + return traces + } + return StackTraceReport(e.Err) +} + // maxHops is a max supported nested depth for errors const maxHops = 50 @@ -387,6 +475,8 @@ type Error interface { ErrorWrapper DebugReporter UserMessager + CompactUserMessager + StackTraceReporter // GetFields returns any fields that have been added to the error GetFields() map[string]interface{} diff --git a/trace_test.go b/trace_test.go index 6b0111f..140b384 100644 --- a/trace_test.go +++ b/trace_test.go @@ -34,8 +34,11 @@ import ( func TestEmpty(t *testing.T) { assert.Equal(t, "", DebugReport(nil)) assert.Equal(t, "", UserMessage(nil)) + assert.Equal(t, "", CompactUserMessage(nil)) assert.Equal(t, "", UserMessageWithFields(nil)) assert.Equal(t, map[string]interface{}{}, GetFields(nil)) + var err TraceErr + assert.Equal(t, Traces{}, err.StackTraceReport()) } func TestWrap(t *testing.T) { @@ -45,7 +48,10 @@ func TestWrap(t *testing.T) { assert.Regexp(t, ".*trace_test.go.*", line(DebugReport(err))) assert.NotRegexp(t, ".*trace.go.*", line(DebugReport(err))) assert.NotRegexp(t, ".*trace_test.go.*", line(UserMessage(err))) + assert.NotRegexp(t, ".*trace_test.go.*", line(CompactUserMessage(err))) assert.Regexp(t, ".*param.*", line(UserMessage(err))) + assert.Regexp(t, ".*param.*", line(CompactUserMessage(err))) + assert.Regexp(t, ".*trace_test.go.*", err.StackTraceReport()[0].Path) } func TestOrigError(t *testing.T) { @@ -67,15 +73,18 @@ func TestWrapUserMessage(t *testing.T) { assert.Regexp(t, ".*trace_test.go.*", line(DebugReport(err))) assert.NotRegexp(t, ".*trace.go.*", line(DebugReport(err))) assert.Equal(t, "user message\tdescription", line(UserMessage(err))) + assert.Equal(t, "user message: description", CompactUserMessage(err)) err = Wrap(err, "user message 2") assert.Equal(t, "user message 2\tuser message\t\tdescription", line(UserMessage(err))) + assert.Equal(t, "user message 2: user message: description", CompactUserMessage(err)) } func TestWrapWithMessage(t *testing.T) { testErr := fmt.Errorf("description") err := WrapWithMessage(testErr, "user message") assert.Equal(t, "user message\tdescription", line(UserMessage(err))) + assert.Equal(t, "user message: description", CompactUserMessage(err)) assert.Regexp(t, ".*trace_test.go.*", line(DebugReport(err))) assert.NotRegexp(t, ".*trace.go.*", line(DebugReport(err))) } @@ -106,6 +115,18 @@ func TestGetFields(t *testing.T) { assert.Equal(t, fields, GetFields(e)) } +func TestStackTraceReport(t *testing.T) { + testErr := fmt.Errorf("description") + assert.Equal(t, Traces{}, StackTraceReport(testErr)) + + err := Wrap(testErr, "user message") + stackTrace := StackTraceReport(err) + assert.Len(t, stackTrace, 3) + assert.Equal(t, stackTrace[0].Func, "github.com/gravitational/trace.TestGetStackTrace") + assert.Equal(t, stackTrace[1].Func, "testing.tRunner") + assert.Equal(t, stackTrace[2].Func, "runtime.goexit") +} + func roundtripError(err error) error { w := newTestWriter() WriteError(w, err) From 353656d83c45e3dcf0e75595aa104ebe2afb2ee9 Mon Sep 17 00:00:00 2001 From: hugoShaka Date: Thu, 28 Nov 2024 16:34:28 -0500 Subject: [PATCH 2/2] fixup! Add CompactUserMessage() and StackTraceReport() --- trace_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trace_test.go b/trace_test.go index 140b384..531c51f 100644 --- a/trace_test.go +++ b/trace_test.go @@ -122,7 +122,7 @@ func TestStackTraceReport(t *testing.T) { err := Wrap(testErr, "user message") stackTrace := StackTraceReport(err) assert.Len(t, stackTrace, 3) - assert.Equal(t, stackTrace[0].Func, "github.com/gravitational/trace.TestGetStackTrace") + assert.Equal(t, stackTrace[0].Func, "github.com/gravitational/trace.TestStackTraceReport") assert.Equal(t, stackTrace[1].Func, "testing.tRunner") assert.Equal(t, stackTrace[2].Func, "runtime.goexit") }