Skip to content

Commit a5cd81e

Browse files
committed
feat(errors): extract pkg/errors stacktraces
1 parent 32cba73 commit a5cd81e

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

Diff for: CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## TBD
44

5+
### Enhancements
6+
7+
* Extract stacktrace contents on errors wrapped by
8+
[`pkg/errors`](https://github.com/pkg/errors).
9+
510
### Bug fixes
611

712
* Send web framework name with severity reason if set. Previously this value was

Diff for: errors/error.go

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package errors
44
import (
55
"bytes"
66
"fmt"
7+
"github.com/pkg/errors"
78
"reflect"
89
"runtime"
910
)
@@ -33,6 +34,11 @@ type ErrorWithStackFrames interface {
3334
StackFrames() []StackFrame
3435
}
3536

37+
type errorWithStack interface {
38+
StackTrace() errors.StackTrace
39+
Error() string
40+
}
41+
3642
// New makes an Error from the given value. If that value is already an
3743
// error then it will be used directly, if not, it will be passed to
3844
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
@@ -48,6 +54,16 @@ func New(e interface{}, skip int) *Error {
4854
Err: e,
4955
stack: e.Callers(),
5056
}
57+
case errorWithStack:
58+
trace := e.StackTrace()
59+
stack := make([]uintptr, len(trace))
60+
for i, ptr := range trace {
61+
stack[i] = uintptr(ptr) - 1
62+
}
63+
return &Error{
64+
Err: e,
65+
stack: stack,
66+
}
5167
case ErrorWithStackFrames:
5268
stack := make([]uintptr, len(e.StackFrames()))
5369
for i, frame := range e.StackFrames() {

Diff for: errors/error_test.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"runtime"
78
"strings"
89
"testing"
10+
11+
"github.com/pkg/errors"
912
)
1013

1114
// fixture functions doing work to avoid inlining
@@ -44,7 +47,7 @@ func TestParsePanicStack(t *testing.T) {
4447
}
4548
expected := []StackFrame{
4649
StackFrame{Name: "TestParsePanicStack.func1", File: "errors/error_test.go"},
47-
StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 13},
50+
StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 16},
4851
}
4952
assertStacksMatch(t, expected, err.StackFrames())
5053
}()
@@ -88,7 +91,7 @@ func TestSkipWorks(t *testing.T) {
8891
}
8992

9093
expected := []StackFrame{
91-
StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 13},
94+
StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 16},
9295
}
9396

9497
assertStacksMatch(t, expected, err.StackFrames())
@@ -182,6 +185,26 @@ func TestNewError(t *testing.T) {
182185
}
183186
}
184187

188+
func TestUnwrapPkgError(t *testing.T) {
189+
_, _, line, ok := runtime.Caller(0) // grab line immediately before error generator
190+
top := func() error {
191+
err := fmt.Errorf("OH NO")
192+
return errors.Wrap(err, "failed") // the correct line for the top of the stack
193+
}
194+
unwrapped := New(top(), 0) // if errors.StackTrace detection fails, this line will be top of stack
195+
if !ok {
196+
t.Fatalf("Something has gone wrong with loading the current stack")
197+
}
198+
if unwrapped.Error() != "failed: OH NO" {
199+
t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
200+
}
201+
expected := []StackFrame{
202+
StackFrame{Name: "TestUnwrapPkgError.func1", File: "errors/error_test.go", LineNumber: line + 3},
203+
StackFrame{Name: "TestUnwrapPkgError", File: "errors/error_test.go", LineNumber: line + 5},
204+
}
205+
assertStacksMatch(t, expected, unwrapped.StackFrames())
206+
}
207+
185208
func ExampleErrorf() {
186209
for i := 1; i <= 2; i++ {
187210
if i%2 == 1 {

0 commit comments

Comments
 (0)