From fe5e3a44599bbe8f2cea5c3f44f874e3344436d2 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Thu, 28 May 2026 15:28:26 +0200 Subject: [PATCH 1/4] perf: reduce allocations in hot paths - framesToStacktraceBlocks/framesToSourceBlocks now accept []outputBlock directly, removing one intermediate slice allocation per Stacktrace() and Sources() call - fix make(0, len)+index-assign bug in Stacktrace() and StackFrames() (would panic at runtime on non-empty error chains) - reuse frameStr variable in framesToStacktraceBlocks (was calling frame.String() twice per frame) - replace 3x strings.Replace in shortFuncName with a package-level strings.Replacer (one pass, no intermediate string allocations) --- error.go | 20 ++++++-------------- stacktrace.go | 31 +++++++++++++++---------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/error.go b/error.go index b7b41c9..e15f163 100644 --- a/error.go +++ b/error.go @@ -529,11 +529,7 @@ func (o OopsError) Stacktrace() string { return "" } - stBlocks := make([]lo.Tuple3[error, string, []oopsStacktraceFrame], len(blocks)) - for i, b := range blocks { - stBlocks[i] = lo.T3(b.err, b.msg, b.frames) - } - return "Oops: " + strings.Join(framesToStacktraceBlocks(stBlocks), "\nThrown: ") + return "Oops: " + strings.Join(framesToStacktraceBlocks(blocks), "\nThrown: ") } // StackFrames returns the raw stack frames as runtime.Frame objects. @@ -543,14 +539,14 @@ func (o OopsError) StackFrames() []runtime.Frame { return nil } filtered := applyFrameSkip(o.stacktrace.frames) - frames := make([]runtime.Frame, 0, len(filtered)) - for _, f := range filtered { - frames = append(frames, runtime.Frame{ + frames := make([]runtime.Frame, len(filtered)) + for i, f := range filtered { + frames[i] = runtime.Frame{ PC: f.pc, File: f.file, Line: f.line, Function: f.function, - }) + } } return frames } @@ -565,11 +561,7 @@ func (o OopsError) Sources() string { return "" } - srcBlocks := make([]lo.Tuple2[string, *oopsStacktrace], len(blocks)) - for i, b := range blocks { - srcBlocks[i] = lo.T2(b.msg, &oopsStacktrace{frames: b.frames}) - } - return "Oops: " + strings.Join(framesToSourceBlocks(srcBlocks), "\n\nThrown: ") + return "Oops: " + strings.Join(framesToSourceBlocks(blocks), "\n\nThrown: ") } // LogValuer returns a slog.Value representation of the error. diff --git a/stacktrace.go b/stacktrace.go index acdf71c..5f5a3a7 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -275,6 +275,9 @@ func newStacktrace(span string, skip int) *oopsStacktrace { // "github.com/user/app.(*Handler).ProcessRequest" -> "ProcessRequest" // "main.main" -> "main" // "github.com/user/pkg.helper" -> "helper" +// ptrReceiverReplacer strips pointer receiver syntax characters from function names. +var ptrReceiverReplacer = strings.NewReplacer("(", "", "*", "", ")", "") + func shortFuncName(longName string) string { // longName is the full function name including package path // Examples of possible formats: @@ -290,12 +293,7 @@ func shortFuncName(longName string) string { // Clean up the function name by removing parentheses and asterisks // that are part of pointer receiver syntax - shortName := withoutPackage - shortName = strings.Replace(shortName, "(", "", 1) // Remove opening parenthesis - shortName = strings.Replace(shortName, "*", "", 1) // Remove asterisk - shortName = strings.Replace(shortName, ")", "", 1) // Remove closing parenthesis - - return shortName + return ptrReceiverReplacer.Replace(withoutPackage) } // applyFrameSkip returns a copy of frames with any entries matching framesSkip patterns removed. @@ -323,21 +321,21 @@ func applyFrameSkip(frames []oopsStacktraceFrame) []oopsStacktraceFrame { return filtered } -func framesToStacktraceBlocks(blocks []lo.Tuple3[error, string, []oopsStacktraceFrame]) []string { +func framesToStacktraceBlocks(blocks []outputBlock) []string { output := make([]string, 0, len(blocks)) shownFrames := make(map[string]bool) for _, e := range blocks { - err := lo.TernaryF(e.A != nil, func() string { return e.A.Error() }, func() string { return "" }) - msg := coalesceOrEmpty(e.B, err, "Error") + err := lo.TernaryF(e.err != nil, func() string { return e.err.Error() }, func() string { return "" }) + msg := coalesceOrEmpty(e.msg, err, "Error") // Build stacktrace for this error, avoiding already shown frames var frameLines []string firstFrame := true // we always show the first frame, because the PC of a recursive function might appear multiple time. - for _, frame := range e.C { + for _, frame := range e.frames { frameStr := frame.String() if !shownFrames[frameStr] || firstFrame { - frameLines = append(frameLines, " --- at "+frame.String()) + frameLines = append(frameLines, " --- at "+frameStr) shownFrames[frameStr] = true } firstFrame = false @@ -353,14 +351,15 @@ func framesToStacktraceBlocks(blocks []lo.Tuple3[error, string, []oopsStacktrace return output } -func framesToSourceBlocks(blocks []lo.Tuple2[string, *oopsStacktrace]) []string { +func framesToSourceBlocks(blocks []outputBlock) []string { output := [][]string{} - for _, e := range blocks { - header, body := e.B.Source() + for _, b := range blocks { + st := oopsStacktrace{frames: b.frames} + header, body := st.Source() - if e.A != "" { - header = fmt.Sprintf("%s\n%s", e.A, header) + if b.msg != "" { + header = fmt.Sprintf("%s\n%s", b.msg, header) } if header != "" && len(body) > 0 { From 9af6314271b30d9e0d8da00906e2beda44dadd7e Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Thu, 28 May 2026 15:31:36 +0200 Subject: [PATCH 2/4] fix(lint): add blank line before var declaration for gofmt --- stacktrace.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stacktrace.go b/stacktrace.go index 5f5a3a7..20c7906 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -275,6 +275,7 @@ func newStacktrace(span string, skip int) *oopsStacktrace { // "github.com/user/app.(*Handler).ProcessRequest" -> "ProcessRequest" // "main.main" -> "main" // "github.com/user/pkg.helper" -> "helper" + // ptrReceiverReplacer strips pointer receiver syntax characters from function names. var ptrReceiverReplacer = strings.NewReplacer("(", "", "*", "", ")", "") From cba484c9805464a3a151f4f353e92a2181384bff Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Thu, 28 May 2026 16:02:06 +0200 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stacktrace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacktrace.go b/stacktrace.go index 20c7906..ae48523 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -353,7 +353,7 @@ func framesToStacktraceBlocks(blocks []outputBlock) []string { } func framesToSourceBlocks(blocks []outputBlock) []string { - output := [][]string{} + output := make([][]string, 0, len(blocks)) for _, b := range blocks { st := oopsStacktrace{frames: b.frames} From 83a12f0da29d0821694eceb279a2172f090a8c2b Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Thu, 28 May 2026 16:02:14 +0200 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stacktrace.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stacktrace.go b/stacktrace.go index ae48523..bcd6ade 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -327,7 +327,10 @@ func framesToStacktraceBlocks(blocks []outputBlock) []string { shownFrames := make(map[string]bool) for _, e := range blocks { - err := lo.TernaryF(e.err != nil, func() string { return e.err.Error() }, func() string { return "" }) + err := "" + if e.err != nil { + err = e.err.Error() + } msg := coalesceOrEmpty(e.msg, err, "Error") // Build stacktrace for this error, avoiding already shown frames