Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TraceCaller interface for extensibility for #104 #106

Merged
merged 9 commits into from
May 6, 2024
19 changes: 18 additions & 1 deletion errtrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func wrap(err error, callerPC uintptr) error {
}

// Format writes the return trace for given error to the writer.
// The output takes a fromat similar to the following:
// The output takes a format similar to the following:
//
// <error message>
//
Expand All @@ -79,6 +79,8 @@ func wrap(err error, callerPC uintptr) error {
// <file>:<line>
// [...]
//
// Any error that has a method `TracePC() uintptr` will
// contribute to the trace.
// If the error doesn't have a return trace attached to it,
// only the error message is reported.
// If the error is comprised of multiple errors (e.g. with [errors.Join]),
Expand All @@ -90,6 +92,8 @@ func Format(w io.Writer, target error) (err error) {
}

// FormatString writes the return trace for err to a string.
// Any error that has a method `TracePC() uintptr` will
// contribute to the trace.
// See [Format] for details of the output format.
func FormatString(target error) string {
var s strings.Builder
Expand Down Expand Up @@ -118,3 +122,16 @@ func (e *errTrace) Format(s fmt.State, verb rune) {

fmt.Fprintf(s, fmt.FormatString(s, verb), e.err)
}

// TracePC returns the program counter for the location
// in the frame that the error originated with.
//
// The returned PC is intended to be used with
// runtime.CallersFrames or runtime.FuncForPC
// to aid in generating the error return trace
func (e *errTrace) TracePC() uintptr {
return e.pc
}

// compile time tracePCprovider interface check
var _ interface{ TracePC() uintptr } = &errTrace{}
20 changes: 16 additions & 4 deletions unwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,30 @@ import "runtime"
//
// You can use this for structured access to trace information.
func UnwrapFrame(err error) (frame runtime.Frame, inner error, ok bool) { //nolint:revive // error is intentionally middle return
e, ok := err.(*errTrace)
e, ok := err.(interface{ TracePC() uintptr })
if !ok {
return runtime.Frame{}, err, false
}

frames := runtime.CallersFrames([]uintptr{e.pc})
wrapErr := unwrapOnce(err)
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
frames := runtime.CallersFrames([]uintptr{e.TracePC()})
f, _ := frames.Next()
if f == (runtime.Frame{}) {
// Unlikely, but if PC didn't yield a frame,
// just return the inner error.
return runtime.Frame{}, e.err, false
return runtime.Frame{}, wrapErr, false
}

return f, e.err, true
return f, wrapErr, true
}

// unwrapOnce accesses the direct cause of the error if any, otherwise
// returns nil.
func unwrapOnce(err error) error {
switch e := err.(type) {
case interface{ Unwrap() error }:
return e.Unwrap()
}

return nil
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
}