From f9eef811e1217ba301d4ae93cfd87146c6518ced Mon Sep 17 00:00:00 2001 From: ztrue Date: Wed, 1 Apr 2026 18:52:08 -0400 Subject: [PATCH 1/5] line numbers padding fixed --- print.go | 13 +++++++++---- print_test.go | 26 +++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/print.go b/print.go index c6d3640..73bfffe 100644 --- a/print.go +++ b/print.go @@ -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) } diff --git a/print_test.go b/print_test.go index ccbd2d8..37449e2 100644 --- a/print_test.go +++ b/print_test.go @@ -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", "", @@ -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()", @@ -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()", @@ -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 {", @@ -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()"), From 54524968b3ca3bda7082e4915150603857aa77b9 Mon Sep 17 00:00:00 2001 From: ztrue Date: Wed, 1 Apr 2026 18:52:29 -0400 Subject: [PATCH 2/5] black numbers updated to be dark grey --- colors.go | 2 +- print_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colors.go b/colors.go index c96999e..d0d6310 100644 --- a/colors.go +++ b/colors.go @@ -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 { diff --git a/print_test.go b/print_test.go index 37449e2..9768fb5 100644 --- a/print_test.go +++ b/print_test.go @@ -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 { From 211c9abe5e0df70779f65d05b2026bce7948951d Mon Sep 17 00:00:00 2001 From: ztrue Date: Wed, 1 Apr 2026 19:22:03 -0400 Subject: [PATCH 3/5] frames resolved lazily --- error.go | 44 +++++++++++++++++++++++++++++--------------- error_test.go | 14 ++++++++++++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/error.go b/error.go index c56913d..9718cd9 100644 --- a/error.go +++ b/error.go @@ -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 } @@ -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 } @@ -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, } } diff --git a/error_test.go b/error_test.go index af194f3..29cce43 100644 --- a/error_test.go +++ b/error_test.go @@ -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 { From 03d3ae018b275c6ce01bd9ecfed75031f136dbac Mon Sep 17 00:00:00 2001 From: ztrue Date: Wed, 1 Apr 2026 19:38:03 -0400 Subject: [PATCH 4/5] lf fixes --- .editorconfig | 2 ++ .gitattributes | 1 + 2 files changed, 3 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..270106b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*] +end_of_line = lf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf From 36980451f878fd1c32f864ded29589ded404e0fa Mon Sep 17 00:00:00 2001 From: ztrue Date: Wed, 1 Apr 2026 19:43:55 -0400 Subject: [PATCH 5/5] readme updated --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 996fce5..40947b7 100644 --- a/README.md +++ b/README.md @@ -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 ```