Skip to content

Commit

Permalink
Add CompactUserMessage() and StackTraceReport()
Browse files Browse the repository at this point in the history
  • Loading branch information
hugoShaka committed Nov 28, 2024
1 parent 88b715e commit 862cdc1
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 0 deletions.
90 changes: 90 additions & 0 deletions trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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{}
Expand Down
21 changes: 21 additions & 0 deletions trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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)))
}
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 862cdc1

Please sign in to comment.