I find Go’s error handling refreshingly simple & clear. Nowadays, all I do is wrap my errors using fmt.Errorf
with some additional context. The pattern I tend to use most of the time just includes a function/method name, like so:
func someFunction() error { err := someStruct{}.someMethod() return fmt.Errorf("someFunction: %w", err) } type someStruct struct { } func (ss someStruct) someMethod() error { return fmt.Errorf("someMethod: %w", errors.New("boom")) }
If I now call someFunction
, the error - when logged or printed - will look like this: someFunction: someMethod: boom
. This gives just enough context to figure out what happened, and where.
I thought to myself - this can be wrapped in a helper function, to automatically take the caller name and do this wrapping for me.
Here’s an implementation:
func WrapWithCaller(err error) error { pc, _, _, ok := runtime.Caller(1) if !ok { return fmt.Errorf("%v: %w", "unknown", err) } fn := runtime.FuncForPC(pc).Name() pkgFunc := path.Base(fn) return fmt.Errorf("%v: %w", pkgFunc, err) }
We can now use it like so:
func someFunction() error { err := someStruct{}.someMethod() return WrapWithCaller(err) } type someStruct struct { } func (ss someStruct) someMethod() error { return WrapWithCaller(errors.New("boom")) }
And it will return the following: main.someFunction: main.someStruct.someMethod: boom
. Keep in mind that this will have performance overhead:
func Benchmark_WrapWithCaller(b *testing.B) { err := errors.New("boom") for i := 0; i < b.N; i++ { _ = WrapWithCaller(err) } } // Result: // Benchmark_WrapWithCaller-10 2801312 427.0 ns/op 344 B/op 5 allocs/op func Benchmark_FmtErrorf(b *testing.B) { err := errors.New("boom") for i := 0; i < b.N; i++ { _ = fmt.Errorf("FmtErrorf: %w", err) } } // Result: // Benchmark_FmtErrorf-10 13475013 87.19 ns/op 48 B/op 2 allocs/op
So it’s 4-5 times slower than a simple wrap using fmt.Errorf
.
One benefit of using WrapWithCaller
is that refactoring is easier - you change the function/method name and don’t need to bother yourself with changing the string in the fmt.Errorf
. The performance hit is likely not an issue for most programs, unless you’re wrapping tons of errors in hot loops or something like that.
I’m a bit torn if I want to add this helper to my codebases and use it everywhere - seems like an extra dependency with little benefit, but I’ll have to give it a spin before I decide if this is something I’m ready to accept. Time will tell. Thanks for reading!
Top comments (0)