Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

get first stack trace info. #195

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
33 changes: 33 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,36 @@ func Cause(err error) error {
}
return err
}

// Stack returns the first stack trace of the errors, if possible.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the phrase “the first stack trace” is ambiguous, depending upon which direction you are orienting the Cause chain. If you look at it going down the Cause chain, the most recent StackTrace is the first stack trace encountered, in which case the stack trace being returned by this function would be the last stack trace.

Meanwhile, from the context that you appear to be intending, you are saying that the bottom-most StackTrace, which is the earliest one attached, is the first, and therefore the last one would be the most-recently attached.

And now even I have reversed my original top/bottom distinction from the last code review, by reorienting the Cause chain. And thus, the difficulty in writing good documentation that explains accurately the contract of behavior.

Please attempt to rework this documentation so that the functionality is unambiguous. Such as, “… returns the earliest/deepest StackTrace attached to any of the errors in the chain of Causes.” Potentially requiring an explanation of what a “Cause chain” is or even how it should be orient for the purpose of understanding the behavior of this function.

Basically, I should be able to read this documentation only, and know the essential functional/behavioral elements of this black box, without having to read the code in order to obtain definitions or context.

Also, this documentation does not indicate what it should return if it is not possible to find a StackTrace. This should be documented so that the user is fully aware of the contract. c.f. the documentation in Cause: If the error does not implement Cause, the original error will be returned. If the error is nil, nil will be returned without further investigation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for your patience. Your advice has been invaluable, and I have followed the suggest expect the last one. I don't know how to deal with this documentation. And I just wrote your words on it. So could you help to make it more clear?

func Stack(err error) StackTrace {
type causer interface {
Cause() error
}

type stackTracer interface {
StackTrace() StackTrace
}

var topStackInfo StackTrace

for {
stackErr, ok := err.(stackTracer)
if ok {
topStackInfo = stackErr.StackTrace()
}

causer, ok := err.(causer)
if !ok {
break
}

err = causer.Cause()
}

if topStackInfo != nil {
return topStackInfo
}

return nil
}
57 changes: 57 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,63 @@ func TestCause(t *testing.T) {
}
}

func printStack(tracer StackTrace) string {
var logInfo string
for _, f := range tracer {
logInfo += fmt.Sprintf("%+s:%d ", f, f)
}
return logInfo
}

func TestStack(t *testing.T) {

x := WithStack(New("error"))
y := x.(*withStack).StackTrace()

tests := []struct {
err error
want StackTrace
}{
{
err: nil,
want: nil,
}, {
err: (error)(nil),
want: nil,
}, {
err: WithStack(x),
want: y,
}, {
err: WithStack(WithStack(x)),
want: y,
}, {
err: Wrap(WithStack(WithStack(x)), "test wrap"),
want: y,
}, {
err: Cause(Wrap(WithStack(WithStack(x)), "test wrap")),
want: y,
}, {
err: Cause(Cause(Wrap(WithStack(WithStack(x)), "test wrap"))),
want: y,
},
}

for i, tt := range tests {
got := Stack(tt.err)
if got == nil || tt.want == nil {
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
}

continue
}

if !reflect.DeepEqual(printStack(got), printStack(tt.want)) {
t.Errorf("test %d: got %#v, want %#v", i+1, printStack(got), printStack(tt.want))
}
}
}

func TestWrapfNil(t *testing.T) {
got := Wrapf(nil, "no error")
if got != nil {
Expand Down