Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avoid creating duplicate stack traces #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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