diff --git a/error.go b/error.go index 9718cd9..d0ef93e 100644 --- a/error.go +++ b/error.go @@ -16,6 +16,7 @@ var DefaultCap = 20 // Error is an error with stack trace. type Error interface { + Callers() []uintptr Error() string StackTrace() []Frame Unwrap() error @@ -38,6 +39,14 @@ func CustomError(err error, frames []Frame) Error { } } +// CustomErrorFromCallers creates an error with provided program counters. +func CustomErrorFromCallers(err error, pcs []uintptr) Error { + return &errorData{ + err: err, + pcs: pcs, + } +} + // Errorf creates new error with stacktrace and formatted message. // Formatting works the same way as in fmt.Errorf. func Errorf(message string, args ...interface{}) Error { @@ -73,6 +82,11 @@ func Unwrap(err error) error { return e.Unwrap() } +// Callers returns raw program counters of the stack trace. +func (e *errorData) Callers() []uintptr { + return e.pcs +} + // Error returns error message. func (e *errorData) Error() string { return e.err.Error() @@ -80,7 +94,7 @@ func (e *errorData) Error() string { // StackTrace resolves and returns the stack trace, caching the result. func (e *errorData) StackTrace() []Frame { - if e.pcs == nil { + if e.frames != nil { return e.frames } cf := runtime.CallersFrames(e.pcs) @@ -97,7 +111,6 @@ func (e *errorData) StackTrace() []Frame { } } e.frames = frames - e.pcs = nil return e.frames } diff --git a/error_test.go b/error_test.go index 29cce43..ba7d9a2 100644 --- a/error_test.go +++ b/error_test.go @@ -235,6 +235,29 @@ func TestCustomError(t *testing.T) { } } +func TestCustomErrorFromCallers(t *testing.T) { + pcs := tracerr.New("some error").Callers() + err := tracerr.CustomErrorFromCallers(errors.New("custom"), pcs) + if len(err.Callers()) != len(pcs) { + t.Errorf("expected %d callers, got %d", len(pcs), len(err.Callers())) + } + if len(err.StackTrace()) == 0 { + t.Error("expected non-empty stack trace") + } +} + +func TestCallers(t *testing.T) { + err := tracerr.New("some error") + pcs := err.Callers() + if len(pcs) == 0 { + t.Error("expected non-empty callers") + } + customErr := tracerr.CustomError(errors.New("custom"), nil) + if customErr.Callers() != nil { + t.Error("expected nil callers for CustomError") + } +} + func TestDeepStack(t *testing.T) { var recurse func(n int) error recurse = func(n int) error {