https://godoc.org/github.com/chaimleib/errors
This Go package is a drop-in replacement to the built-in errors
package. It is designed for Go 1.13, while under 1.12, the Is
, As
and Unwrap
functions have been backported.
Error messages exist to help you fix the problem. I kept finding that regular error messages required too much searching to understand the issue. I like this better:
main.main() main.go:34 error getting user profile
~/client.UserProfile(ctx, "alice") userprofile.go:124 error authenticating
~/client.Authenticate(ctx, "bob", pw) client.go:63 password expired
Your
go.mod
main module name is abbreviated as~
.
That's much more helpful than this:
password expired
What operation required a password? Where did this get generated? How did we get there?
With enhanced errors, you can begin fixing the problem right away:
- You know exactly where the error came from.
- You know what code path led to the error.
- You know what inputs each function was seeing.
All this, without having to use rg
or open up any files!
- At the beginning of your methods, have this:
func (client Client) Authenticate(ctx context.Context, user, pw string) (UserProfile, error) {
b := errors.NewBuilder("ctx, %q, pw", user)
// Note: You don't have to show values for all the arguments.
// An idea: for brevity, try "[%d]aSlice", len(aSlice) instead of showing the
// whole slice or map, etc.
- Whenever you return an error:
if err != nil {
return nil, b.Wrap(err, "error parsing %q", value)
}
- At the top of the program, print the full stack trace:
if err != nil {
fmt.Println(errors.StackString(err))
}
Another example (try me!):
func main() {
b := errors.NewBuilder("")
if err := FileHasHello("greet.txt"); err != nil {
err = b.Wrap(err, "program failed")
fmt.Println(errors.StackString(err))
return
}
}
func FileHasHello(fpath string) error {
b := errors.NewBuilder("%q", fpath)
buf, err := ioutil.ReadFile(fpath)
if err != nil {
return b.Wrap(err, "could not open file")
}
if !bytes.Contains(buf, []byte("hello")) {
return b.Errorf("could not find `hello` in file")
}
return nil
}
/* output:
main.main() prog.go:14 program failed
main.FileHasHello("greet.txt") prog.go:24 could not open file
open greet.txt: No such file or directory
No such file or directory
*/
-
Print just one level of stack trace using
StackStringAt(err)
. Index intoStack(err)
to select an error by itsUnwrap()
depth. -
Use
Is
,As
andUnwrap
in Go 1.12 (added officially in Go 1.13) -
Group errors (try me)
errs := []error{
fmt.Errorf("server A failed"),
fmt.Errorf("server B failed"),
}
wrapped := errors.Wrap(errors.Group(errs), "all servers failed")
fmt.Println(errors.StackString(wrapped))
// all servers failed
// [
// server A failed
// ,
// server B failed
// ]
- Get call site info with
NewFuncInfo(calldepth)
. (This is for debugging output only. It is bad design to write application logic around these values.)
fi := errors.NewFuncInfo(1)
fmt.Println(fi.File(), fi.Line(), fi.FuncName())
// /absolute/path/to/main.go 12 main.main
- Customize your stack trace formatting by writing your our
StackString()
function. All the necessary plumbing (likeFuncInfo()
,ArgStringer()
andWrapper()
) is exposed as public methods.
Copyright 2019-2020 Chaim Leib Halbert
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this source code except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.