@@ -2,29 +2,9 @@ package rogerr
22
33import (
44 "context"
5- "errors"
65 "fmt"
7- "runtime"
8- "runtime/debug"
9- "strings"
106)
117
12- // Frame represents a single frame in a stacktrace.
13- type Frame struct {
14- File string // Full file path
15- Line int // Line number
16- Function string // Function or method name
17- InApp bool // true if application code, false if dependency
18- }
19-
20- // ErrorHandler provides configurable error handling with optional stacktrace capture.
21- type ErrorHandler struct {
22- stacktrace bool
23- }
24-
25- // Option is a function that configures an ErrorHandler.
26- type Option func (* ErrorHandler )
27-
288type rError struct {
299 err error
3010 ctx context.Context
@@ -54,157 +34,9 @@ func (e *rError) Unwrap() error {
5434 return e .err
5535}
5636
57- // getModulePath returns the main module path for determining if frames are in-app.
58- func getModulePath () string {
59- if bi , ok := debug .ReadBuildInfo (); ok {
60- return bi .Main .Path
61- }
62- return ""
63- }
64-
65- // captureStacktrace captures the current call stack, excluding rogerr internal frames.
66- func captureStacktrace (modulePath string ) []Frame {
67- const maxFrames = 64
68- ptrs := [maxFrames ]uintptr {}
69-
70- // Skip 0 frames as we'll filter manually
71- pcs := ptrs [0 :runtime .Callers (0 , ptrs [:])]
72-
73- allFrames := make ([]Frame , 0 , len (pcs ))
74- iter := runtime .CallersFrames (pcs )
75-
76- // First, collect all frames
77- for {
78- frame , more := iter .Next ()
79-
80- // Determine if this is application code
81- inApp := isInApp (frame .Function , modulePath )
82-
83- allFrames = append (allFrames , Frame {
84- File : frame .File ,
85- Line : frame .Line ,
86- Function : frame .Function ,
87- InApp : inApp ,
88- })
89-
90- if ! more {
91- break
92- }
93- }
94-
95- // Now filter out rogerr frames, but keep everything after the last rogerr frame
96- lastRogerrIndex := - 1
97- for i , frame := range allFrames {
98- // Only filter out the main rogerr package, not internal modules
99- if strings .HasPrefix (frame .Function , "github.com/kinbiko/rogerr." ) {
100- lastRogerrIndex = i
101- }
102- }
103-
104- // Return frames after the last rogerr frame
105- if lastRogerrIndex >= 0 && lastRogerrIndex + 1 < len (allFrames ) {
106- return allFrames [lastRogerrIndex + 1 :]
107- }
108-
109- // If no rogerr frames found, return all frames (shouldn't happen)
110- return allFrames
111- }
112-
113- // isInApp determines if a function belongs to the application or a dependency.
114- func isInApp (function , modulePath string ) bool {
115- if modulePath == "" {
116- return false
117- }
118-
119- // Special case for main.main
120- if strings .Contains (function , "main.main" ) {
121- return true
122- }
123-
124- // Check if function belongs to the main module
125- if strings .HasPrefix (function , modulePath ) {
126- return true
127- }
128-
129- // Handle case where the binary is built from a module and function names
130- // start with "main." - check if the module path contains the current module
131- if strings .HasPrefix (function , "main." ) {
132- return true
133- }
134-
135- return false
136- }
137-
138- // WithStacktrace configures whether stacktraces should be captured.
139- func WithStacktrace (enabled bool ) Option {
140- return func (h * ErrorHandler ) {
141- h .stacktrace = enabled
142- }
143- }
144-
145- // NewErrorHandler creates a new ErrorHandler with the given options.
146- // By default, stacktrace capture is enabled.
147- func NewErrorHandler (opts ... Option ) * ErrorHandler {
148- h := & ErrorHandler {
149- stacktrace : true , // stacktrace enabled by default
150- }
151- for _ , opt := range opts {
152- opt (h )
153- }
154- return h
155- }
156-
157- // Stacktrace extracts the stacktrace from an error if it was created with ErrorHandler.
158- func (h * ErrorHandler ) Stacktrace (err error ) []Frame {
159- rErr := & rError {}
160- if errors .As (err , & rErr ) {
161- return rErr .stacktrace
162- }
163- return nil
164- }
165-
166- // Wrap attaches ctx data and wraps the given error with message, optionally capturing stacktrace.
167- // ctx, err, and msgAndFmtArgs are all optional, but at least one must be given
168- // for this function to return a non-nil error.
169- // Any attached diagnostic data from this ctx will be preserved should you
170- // pass the returned error further up the stack.
171- func (h * ErrorHandler ) Wrap (ctx context.Context , err error , msgAndFmtArgs ... interface {}) error {
172- if ctx == nil && err == nil && msgAndFmtArgs == nil {
173- return nil
174- }
175-
176- e := & rError {err : err , ctx : ctx }
177-
178- if l := len (msgAndFmtArgs ); l > 0 {
179- if msg , ok := msgAndFmtArgs [0 ].(string ); ok {
180- e .msg = fmt .Sprintf (msg , msgAndFmtArgs [1 :]... )
181- }
182- }
183-
184- // Capture stacktrace if enabled
185- if h .stacktrace {
186- e .stacktrace = captureStacktrace (getModulePath ())
187- }
188-
189- return e
190- }
191-
192- // Wrap attaches ctx data and wraps the given error with message.
193- // ctx, err, and msgAndFmtArgs are all optional, but at least one must be given
194- // for this function to return a non-nil error.
195- // Any attached diagnostic data from this ctx will be preserved should you
196- // pass the returned error further up the stack.
37+ // Wrap wraps errors with the default error handler settings.
38+ // See ErrorHandler.Wrap for more details.
19739// Deprecated: Use ErrorHandler.Wrap instead.
198- func Wrap (ctx context.Context , err error , msgAndFmtArgs ... interface {}) error {
199- if ctx == nil && err == nil && msgAndFmtArgs == nil {
200- return nil
201- }
202- e := & rError {err : err , ctx : ctx }
203-
204- if l := len (msgAndFmtArgs ); l > 0 {
205- if msg , ok := msgAndFmtArgs [0 ].(string ); ok {
206- e .msg = fmt .Sprintf (msg , msgAndFmtArgs [1 :]... )
207- }
208- }
209- return e
40+ func Wrap (ctx context.Context , err error , msgAndFmtArgs ... any ) error {
41+ return NewErrorHandler ().Wrap (ctx , err , msgAndFmtArgs ... )
21042}
0 commit comments