Skip to content

Commit

Permalink
avoid creating duplicate stack traces
Browse files Browse the repository at this point in the history
This is done by using the new AddStack function instead of WithStack.
There is also a StackError utility function exposed.
  • Loading branch information
gregwebs committed Sep 5, 2018
1 parent 816c908 commit 0a61de8
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 11 deletions.
46 changes: 35 additions & 11 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,7 @@
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface.
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// invoked. This information can be retrieved with the StackTracer interface that returns a StackTrace.
// Where errors.StackTrace is defined as
//
// type StackTrace []Frame
Expand All @@ -79,15 +74,12 @@
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// if stacked := errors.GetStackTracer(err); stacked != nil {
// for _, f := range stacked.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// stackTracer interface is not exported by this package, but is considered a part
// of stable public API.
//
// See the documentation for Frame.Format for more details.
package errors

Expand Down Expand Up @@ -145,12 +137,44 @@ func WithStack(err error) error {
if err == nil {
return nil
}

return &withStack{
err,
callers(),
}
}

// AddStack is similar to WithStack.
// However, it will first check to see if a stack trace already exists in the causer chain before creating another one.
// It does this by using the StackError function.
func AddStack(err error) error {
if GetStackTracer(err) != nil {
return err
}
return WithStack(err)
}

// GetStackTracer will return the first StackTracer in the causer chain.
// This function is used by AddStack to avoid creating redundant stack traces.
//
// You can also use the StackTracer interface on the returned error to get the stack trace.
func GetStackTracer(err error) StackTracer {
type causer interface {
Cause() error
}
for err != nil {
if stacked, ok := err.(StackTracer); ok {
return stacked
}
cause, ok := err.(causer)
if !ok {
return nil
}
err = cause.Cause()
}
return nil
}

type withStack struct {
error
*stack
Expand Down
56 changes: 56 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ func TestCause(t *testing.T) {
}, {
WithStack(io.EOF),
io.EOF,
}, {
AddStack(nil),
nil,
}, {
AddStack(io.EOF),
io.EOF,
}}

for i, tt := range tests {
Expand Down Expand Up @@ -154,6 +160,10 @@ func TestWithStackNil(t *testing.T) {
if got != nil {
t.Errorf("WithStack(nil): got %#v, expected nil", got)
}
got = AddStack(nil)
if got != nil {
t.Errorf("AddStack(nil): got %#v, expected nil", got)
}
}

func TestWithStack(t *testing.T) {
Expand All @@ -173,6 +183,50 @@ func TestWithStack(t *testing.T) {
}
}

func TestAddStack(t *testing.T) {
tests := []struct {
err error
want string
}{
{io.EOF, "EOF"},
{AddStack(io.EOF), "EOF"},
}

for _, tt := range tests {
got := AddStack(tt.err).Error()
if got != tt.want {
t.Errorf("AddStack(%v): got: %v, want %v", tt.err, got, tt.want)
}
}
}

func TestGetStackTracer(t *testing.T) {
orig := io.EOF
if GetStackTracer(orig) != nil {
t.Errorf("GetStackTracer: got: %v, want %v", GetStackTracer(orig), nil)
}
stacked := AddStack(orig)
if GetStackTracer(stacked).(error) != stacked {
t.Errorf("GetStackTracer(stacked): got: %v, want %v", GetStackTracer(stacked), stacked)
}
final := AddStack(stacked)
if GetStackTracer(final).(error) != stacked {
t.Errorf("GetStackTracer(final): got: %v, want %v", GetStackTracer(final), stacked)
}
}

func TestAddStackDedup(t *testing.T) {
stacked := WithStack(io.EOF)
err := AddStack(stacked)
if err != stacked {
t.Errorf("AddStack: got: %+v, want %+v", err, stacked)
}
err = WithStack(stacked)
if err == stacked {
t.Errorf("WithStack: got: %v, don't want %v", err, stacked)
}
}

func TestWithMessageNil(t *testing.T) {
got := WithMessage(nil, "no error")
if got != nil {
Expand Down Expand Up @@ -215,6 +269,8 @@ func TestErrorEquality(t *testing.T) {
WithMessage(io.EOF, "whoops"),
WithStack(io.EOF),
WithStack(nil),
AddStack(io.EOF),
AddStack(nil),
}

for i := range vals {
Expand Down
6 changes: 6 additions & 0 deletions stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import (
"strings"
)

// StackTracer retrieves the StackTrace
// Generally you would want to use the GetStackTracer function to do that.
type StackTracer interface {
StackTrace() StackTrace
}

// Frame represents a program counter inside a stack frame.
type Frame uintptr

Expand Down

0 comments on commit 0a61de8

Please sign in to comment.