Skip to content

Repository files navigation

errutil

Go Reference

Why?

While numerous error packages provide rich functionality, errutil is the minimal (opinionated) functionality GRAX needs for error traces. Minimal functionality leads to:

  • Consistent use through a codebase.
  • A faster implementation.
  • No assumptions to break. For example, when errors with meaning (UserNotFound) are offered.

Usage

With and friends produce an error containing location information. The locations in a chain of errors will surface in the Stack produced by BuildStack. The stack can then be logged, displayed, sent to a service etc.

The Wrap methods additionally wrap passed errors so errors.Is matches the original error. To understand when Wrap should be used instead of With, read the Whether to Wrap section of the Go 1.13 errors blog post.

Functions that do not expose Is/As errors as part of their contract, should look similar to:

funcaFunc() error {
...returnerrutil.With(err)
}

Wrapping sentinel errors, should look similar to:

varErrNotFound=errors.New("not found")
...funcaFunc() error {
iferr:=bFunc(); err!=nil {
returnerrutil.Wrap(err, ErrNotFound)
}
...
}
iferr:=aFunc(); err!=nil {
iferrors.Is(err, ErrNotFound) {
// handle not found case
}
...
}

Wrapping unknown errors (discouraged), should look similar to:

iferr:=aFunc(); err!=nil {
returnerrutil.Wrap(err)
}

Wrapping custom errors that are not sentinels, should look similar to:

typeCustomErrorstruct {
Textstring
}
func (eCustomError) Error() string {
return"text: "+e.Text
}
// Must use a pointer to avoid accidental matches. Does not need to be the same type as CustomError.varErrCustom=errors.New("custom error")
func (CustomError) Is(targeterror) bool {
returntarget==ErrCustom
}
funcaFunc() error {
iferr:=bFunc(); err!=nil {
returnerrutil.Wrap(err, ErrCustom)
}
... }
iferr:=aFunc(); err!=nil {
varcErrCustomErroriferrors.As(err, &cErr) {
// use cErr.Text
}
...
}

Custom errors should implement Baser or errors.Unwrap to maintain traces, similar to:

typeCustomErrorstruct {
ErrerrorTextstring
}
func (eCustomError) Error() string {
return"text: "+e.Text
}
func (eCustomError) Base() error {
returne.Err
}

Simple logging could be done with:

iferr:=topOfCalls(); err!=nil {
log.Println(errutil.BuildStack(err))
}

Tools

The tools/ directory is a separate Go module of static analyzers that enforce the usage patterns above.

Disclaimer: these analyzers were written largely with the assistance of LLM tooling. Their SSA-based dataflow reasoning is subtle, so review and test changes with care.

State: two analyzers (errwrap, errunchecked), SSA-based, distributed as standalone binaries under cmd/. Both report unused suppression directives as failures.

The rule summaries below are intentionally brief. The testdata/src/a/a.go file for each analyzer is the authoritative specification; every accepted and flagged case is a labeled example, and reviewing it directly is the most complete demonstration of the rules.

errwrap — testdata

Every returned error must carry errutil location info.

  • unwrapped — a returned error that isn't wrapped (return err, return errors.New(...), a bare field/map/index/call result).
  • new — wrapping errors.New/fmt.Errorf; use errutil.New instead.

Accepted (not flagged): nil, any value traced back to an errutil wrap, Base() error accessors, and struct fields whose every assignment is wrapped.

errunchecked — testdata

  • uncheckederrutil.With/Wrap applied to a call result with no preceding nil check (e.g. return errutil.With(f())), which would wrap a possibly-nil error.

Accepted: the wrap sits behind a recognized nil check — err != nil, sentinel equality, errors.Is/As, comma-ok assertions, ctx.Err(), provably-non-nil bool predicates, or a nil-preserving helper.

Suppressing

Place //errutil:unwrapped, //errutil:new, or //errutil:unchecked on the statement (any line of a multiline call/return), the line above it, on the function, or before the package declaration (file-wide). Generated files (// Code generated ... DO NOT EDIT.) are not checked, though their functions still contribute non-nil facts to errunchecked.

Install & run

go install github.com/graxinc/errutil/tools/errwrap/cmd/errwrap@latest
go install github.com/graxinc/errutil/tools/errunchecked/cmd/errunchecked@latest
errwrap ./...
errunchecked ./...

Future improvements

  • Garbage reduction. Currently we maintain pointer equality in the same vein as errors.New as developers likely expect, however it requires heap allocation. This only shows up however in very fast loops.

About

Minimal fast error traces

Resources

Stars

5 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages