Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*]
end_of_line = lf
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,18 @@ err = err.Unwrap()

## Performance

Stack trace causes a performance overhead, depending on a stack trace depth. This can be insignificant in a number of situations (such as HTTP request handling), however, avoid of adding a stack trace for really hot spots where a high number of errors created frequently, this can be inefficient.

> Benchmarks done on a MacBook Pro 2015 with go 1.11.

Benchmarks for creating a new error with a stack trace of different depth:

```
BenchmarkNew/5 200000 5646 ns/op 976 B/op 4 allocs/op
BenchmarkNew/10 200000 11565 ns/op 976 B/op 4 allocs/op
BenchmarkNew/20 50000 25629 ns/op 976 B/op 4 allocs/op
BenchmarkNew/40 20000 65833 ns/op 2768 B/op 5 allocs/op
GOMAXPROCS=1 go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: github.com/ztrue/tracerr
cpu: Intel(R) Core(TM) i7-14700KF
BenchmarkNew/5 4500129 267.1 ns/op 256 B/op 4 allocs/op
BenchmarkNew/10 3325456 359.5 ns/op 256 B/op 4 allocs/op
BenchmarkNew/20 1000000 1001 ns/op 576 B/op 5 allocs/op
BenchmarkNew/40 538689 2171 ns/op 1216 B/op 6 allocs/op
PASS
ok github.com/ztrue/tracerr 5.246s
```
2 changes: 1 addition & 1 deletion colors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func bold(in string) string {
}

func black(in string) string {
return color(30, in)
return color(90, in)
}

func red(in string) string {
Expand Down
44 changes: 29 additions & 15 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ type Error interface {
type errorData struct {
// err contains original error.
err error
// frames contains stack trace of an error.
// pcs contains raw program counters, resolved lazily to frames.
pcs []uintptr
// frames contains pre-resolved stack trace.
frames []Frame
}

Expand Down Expand Up @@ -76,8 +78,26 @@ func (e *errorData) Error() string {
return e.err.Error()
}

// StackTrace returns stack trace of an error.
// StackTrace resolves and returns the stack trace, caching the result.
func (e *errorData) StackTrace() []Frame {
if e.pcs == nil {
return e.frames
}
cf := runtime.CallersFrames(e.pcs)
frames := make([]Frame, 0, len(e.pcs))
for {
f, more := cf.Next()
frames = append(frames, Frame{
Func: f.Function,
Line: f.Line,
Path: f.File,
})
if !more {
break
}
}
e.frames = frames
e.pcs = nil
return e.frames
}

Expand Down Expand Up @@ -112,23 +132,17 @@ func (f Frame) String() string {
}

func trace(err error, skip int) Error {
frames := make([]Frame, 0, DefaultCap)
pcs := make([]uintptr, DefaultCap)
for {
pc, path, line, ok := runtime.Caller(skip)
if !ok {
n := runtime.Callers(skip+1, pcs)
if n < len(pcs) {
pcs = pcs[:n]
break
}
fn := runtime.FuncForPC(pc)
frame := Frame{
Func: fn.Name(),
Line: line,
Path: path,
}
frames = append(frames, frame)
skip++
pcs = make([]uintptr, len(pcs)*2)
}
return &errorData{
err: err,
frames: frames,
err: err,
pcs: pcs,
}
}
14 changes: 14 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,20 @@ func TestCustomError(t *testing.T) {
}
}

func TestDeepStack(t *testing.T) {
var recurse func(n int) error
recurse = func(n int) error {
if n == 0 {
return tracerr.New("deep error")
}
return recurse(n - 1)
}
err := recurse(25).(tracerr.Error)
if len(err.StackTrace()) < 25 {
t.Errorf("expected at least 25 frames, got %d", len(err.StackTrace()))
}
}

func TestErrorNil(t *testing.T) {
wrapped := wrapError(nil)
if wrapped != nil {
Expand Down
13 changes: 9 additions & 4 deletions print.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,27 @@ func sourceRows(rows []string, frame Frame, before, after int, colorized bool) [
current := frame.Line - 1
start := current - before
end := current + after
maxLine := end + 1
if maxLine > len(lines) {
maxLine = len(lines)
}
width := len(strconv.Itoa(maxLine))
for i := start; i <= end; i++ {
if i < 0 || i >= len(lines) {
continue
}
line := lines[i]
var message string
// TODO Pad to the same length.
lineNum := fmt.Sprintf("%*d", width, i+1)
if i == frame.Line-1 {
message = fmt.Sprintf("%d\t%s", i+1, line)
message = fmt.Sprintf("%s\t%s", lineNum, line)
if colorized {
message = red(message)
}
} else if colorized {
message = fmt.Sprintf("%s\t%s", black(strconv.Itoa(i+1)), line)
message = fmt.Sprintf("%s\t%s", black(lineNum), line)
} else {
message = fmt.Sprintf("%d\t%s", i+1, line)
message = fmt.Sprintf("%s\t%s", lineNum, line)
}
rows = append(rows, message)
}
Expand Down
28 changes: 14 additions & 14 deletions print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ func TestPrint(t *testing.T) {
"15\t",
"",
"/tracerr/error_helper_test.go:9 github.com/ztrue/tracerr_test.addFrameA()",
"6\t)",
"7\t",
"8\tfunc addFrameA(message string) error {",
"9\t\treturn addFrameB(message)",
" 6\t)",
" 7\t",
" 8\tfunc addFrameA(message string) error {",
" 9\t\treturn addFrameB(message)",
"10\t}",
"11\t",
"",
Expand Down Expand Up @@ -124,9 +124,9 @@ func TestPrint(t *testing.T) {
"14\t}",
"",
"/tracerr/error_helper_test.go:9 github.com/ztrue/tracerr_test.addFrameA()",
"7\t",
"8\tfunc addFrameA(message string) error {",
"9\t\treturn addFrameB(message)",
" 7\t",
" 8\tfunc addFrameA(message string) error {",
" 9\t\treturn addFrameB(message)",
"10\t}",
"",
"/tracerr/print_test.go:26 github.com/ztrue/tracerr_test.TestPrint()",
Expand Down Expand Up @@ -159,9 +159,9 @@ func TestPrint(t *testing.T) {
"14\t}",
"",
"/tracerr/error_helper_test.go:9 github.com/ztrue/tracerr_test.addFrameA()",
"7\t",
"8\tfunc addFrameA(message string) error {",
"9\t\treturn addFrameB(message)",
" 7\t",
" 8\tfunc addFrameA(message string) error {",
" 9\t\treturn addFrameB(message)",
"10\t}",
"",
"/tracerr/print_test.go:26 github.com/ztrue/tracerr_test.TestPrint()",
Expand Down Expand Up @@ -217,7 +217,7 @@ func TestPrint(t *testing.T) {
"17\t\treturn tracerr.New(message)",
"",
"/tracerr/error_helper_test.go:9 github.com/ztrue/tracerr_test.addFrameA()",
"9\t\treturn addFrameB(message)",
" 9\t\treturn addFrameB(message)",
"10\t}",
"11\t",
"12\tfunc addFrameB(message string) error {",
Expand Down Expand Up @@ -251,8 +251,8 @@ func TestPrint(t *testing.T) {
black("14") + "\t}",
"",
bold("/tracerr/error_helper_test.go:9 github.com/ztrue/tracerr_test.addFrameA()"),
black("8") + "\tfunc addFrameA(message string) error {",
red("9\t\treturn addFrameB(message)"),
black(" 8") + "\tfunc addFrameA(message string) error {",
red(" 9\t\treturn addFrameB(message)"),
black("10") + "\t}",
"",
bold("/tracerr/print_test.go:26 github.com/ztrue/tracerr_test.TestPrint()"),
Expand Down Expand Up @@ -459,7 +459,7 @@ func bold(in string) string {
}

func black(in string) string {
return fmt.Sprintf("\x1b[30m%s\x1b[0m", in)
return fmt.Sprintf("\x1b[90m%s\x1b[0m", in)
}

func red(in string) string {
Expand Down
Loading