errors

package
v0.0.46-2e8eb6d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2025 License: Apache-2.0 Imports: 11 Imported by: 4

Documentation

Overview

Package errors prefixes the calling functions name to errors for simpler, smaller traces. This package tries to split the difference between github.com/pkg/errors and Go stdlib errors, with first class support for log/slog.

Example
setup()
// This example showcases how to use structured errors alongside log/slog.
err := baby()
if err != nil {
	// include some metadata about this failure
	err = WrapAttr(err, slog.String("don't", "hurt me"), slog.String("no", "more"))
}
// Typically this error would then bubble up through a few more function calls.
// Could be wrapped many more times, but eventually something handles this error.
// For exanple, it can be logged
if err != nil {
	slog.Warn("what is love", "err", err)
}
// Pulling out metadata from a context is also possible, useful for attaching something like request IDs to any error from a request handler.
ctx := AddAttrToCtx(context.Background(), slog.Uint64("answer", 42))
// WrapAttrCtxAfter is an simple and maintainable way to add context metadata to any error returned from a function.
// Here is a small function that hashes and writes some random bytes to showcase various error helper functions from this package.
_, err = func(ctx context.Context, file string) (_ int, err error) {
	dest := path.Join(os.TempDir(), "hashed.brown")
	defer WrapAttrCtxAfter(ctx, &err, slog.String("input", file), slog.String("output", dest))
	fileBytes := make([]byte, 10)
	// Scrounge up some bytes
	bytesRead, err := rand.NewChaCha8([32]byte{}).Read(fileBytes)
	if err != nil {
		return 0, Wrapf(err, "failed to generate bytes")
	}
	// Ensure we track how much we read in case that's relevant later
	defer WrapAttrCtxAfter(ctx, &err, slog.Int("bytes_read", bytesRead))

	hash := sha256.Sum256(fileBytes)
	// Open this file for writing... or reading... whatever.
	f, err := os.OpenFile(path.Clean(dest), os.O_RDONLY|os.O_CREATE, 0600)
	if err != nil {
		return 0, Wrapf(err, "failed os.OpenFile as read only")
	}
	// JoinAfter helps you respect errors from commonly ignored functions like Close.
	defer JoinAfter(&err, f.Close)
	// Wrap* functions return nil if the err is nil, so the last if err != nil statement can typically be replaced and simplified.
	// The smaller functions you write, the more you can take advantage of this.
	bytesWritten, err := f.Write(hash[:])
	return bytesWritten, Wrapf(err, "failed os.WriteFile")
}(ctx, path.Join(os.TempDir(), "hash.brown"))

if err != nil {
	slog.LogAttrs(ctx, slog.LevelError, "hash browns burnt", slog.Any("err", err))
}

// printing the error with something like fmt.Println won't include the metadata in the output.
fmt.Println(err)
err = Wrapf(err, "doubleWrap")
// unless you use %+v
fmt.Printf("%+v", err)
Output:

level=WARN msg="what is love" err.msg="errors.baby don't hurt me" err.don't="hurt me" err.no=more err.source=github.com/danlock/pkg/errors/attr_test.go:32
level=ERROR msg="hash browns burnt" err.msg="errors.Example.func1 failed os.WriteFile write /tmp/hashed.brown: bad file descriptor" err.answer=42 err.bytes_read=10 err.input=/tmp/hash.brown err.output=/tmp/hashed.brown err.source=github.com/danlock/pkg/errors/attr_test.go:75
errors.Example.func1 failed os.WriteFile write /tmp/hashed.brown: bad file descriptor
[msg=errors.Example doubleWrap errors.Example.func1 failed os.WriteFile write /tmp/hashed.brown: bad file descriptor answer=42 bytes_read=10 input=/tmp/hash.brown output=/tmp/hashed.brown source=github.com/danlock/pkg/errors/attr_test.go:75]

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// DefaultSourceSlogKey is the default slog.Attr key used for file:line information when an error is printed.
	// If set to "", file:line metadata will not be included in errors.
	DefaultSourceSlogKey = slog.SourceKey

	// DefaultMsgSlogKey is the default slog.Attr key used for the error message when an error is printed.
	// If set to "", the error message will not be included in the slog.LogValuer group.
	DefaultMsgSlogKey = slog.MessageKey

	// DefaultPackagePrefix controls the trimming of the build location out of the file:line source.
	// With Go modules it's updated automatically, but without Go modules it defaults to github.com/ and may need to be updated for your project.
	// If set to "" the source path is not trimmed at all.
	//
	// trimming example: /home/dan/go/src/github.com/danlock/pkg/errors/attr_test.go:30 -> github.com/danlock/pkg/errors/attr_test.go:30
	DefaultPackagePrefix = "github.com/"

	// AttrCompareSortFunc controls how an errors LogValue output will be sorted for determinism.
	// By default log output is nondeterministic because an error's slog.Attr order can change.
	// Regardless of this value msg will be first and source will be last.
	// Example usage:
	//
	//	errors.AttrCompareSortFunc = func(a, b slog.Attr) int { return cmp.Compare(a.Key, b.Key) }
	AttrCompareSortFunc func(slog.Attr, slog.Attr) int
)

Various options configuring the behavior of this package. Set before error creation.

View Source
var ErrUnsupported = errors.ErrUnsupported

ErrUnsupported indicates that a requested operation cannot be performed, because it is unsupported. Calls stdlib errors.ErrUnsupported

Functions

func AddAttrToCtx

func AddAttrToCtx(ctx context.Context, meta ...slog.Attr) context.Context

AddAttrToCtx adds metadata to the context. Existing context metadata willl be carried forth in the new context.

The only way to retrieve the metadata is with UnwrapAttr on an error wrapped with WrapAttrCtx, or by just slogging the error which handles this internally.

If you are interested in pulling values out of the context for other purposes, take a look at https://github.com/veqryn/slog-context instead.

func As

func As(err error, target any) bool

As finds the first error in err's tree that matches target, and if one is found, sets target to that error value and returns true. Otherwise, it returns false. Calls stdlib errors.As

func Errorf

func Errorf(format string, a ...any) error

Errorf is like fmt.Errorf with the "package.func" of it's caller prepended. It also includes the file and line info of it's caller.

func ErrorfWithSkip

func ErrorfWithSkip(skip int, format string, a ...any) error

ErrorfWithSkip is like fmt.Errorf with the "package.func" of the desired caller prepended. It also includes the file and line info of it's caller.

func Get

func Get[T any](meta map[string]slog.Value, key string) (val T, err error)

Get retrieves a value from the error's metadata. Returns an error if the key doesn't exist or the type is incorrect instead of panicking like the slog.Value methods do. This can be used for retrieving an error code added to the error chain, for example Get[uint64](meta, "http.code")

Note that meta["http.code"].Uint64() is more efficient in regards to allocations.

func Into

func Into[T any](err error) (val T, ok bool)

Into finds the first error in err's chain that matches target type T, and if so, returns it. Into is a type-safe alternative to As.

func Is

func Is(err error, target error) bool

Is reports whether any error in err's tree matches target. Calls stdlib errors.Is

func Join

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if every value in errs is nil

func JoinAfter

func JoinAfter(errPtr *error, errFuncs ...func() error)

JoinAfter returns an error that wraps the given deferred errors. JoinAfter only updates errPtr if one of the errFuncs returned an error. errPtr must point to the named error return value from the calling function.

func Must

func Must[T any](val T, err error) T

Must is a generic helper, like template.Must, that wraps a call to a function returning (T, error) and panics if the error is non-nil.

func New

func New(text string) error

New creates a new error with the package.func of it's caller prepended. It also includes the file and line info of it's caller.

func Unwrap

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil. Calls stdlib errors.Unwrap

func UnwrapAttr

func UnwrapAttr(err error) map[string]slog.Value

UnwrapAttr returns a map around an error chain's metadata. If the error lacks metadata an empty map is returned. Since errors in this package implement slog.LogValuer you don't need to use this to pass slog.Attr to slog.Log.

Structured errors can be introspected and handled differently as needed.

func Wrap

func Wrap(err error) error

Wrap wraps an error with the caller's package.func prepended. Similar to github.com/pkg/errors.Wrap and unlike fmt.Errorf it returns nil if err is nil. If not wrapping an error from this Go package it also includes the file and line info of it's caller.

func WrapAttr

func WrapAttr(err error, meta ...slog.Attr) error

WrapAttr is WrapAttrCtx without the context.

func WrapAttrCtx

func WrapAttrCtx(ctx context.Context, err error, meta ...slog.Attr) error

WrapAttrCtx wraps an error with metadata for structured logging. Similar to github.com/pkg/errors.Wrap and unlike fmt.Errorf it returns nil if err is nil.

If not wrapping an error from this Go package it also includes the file and line info of it's caller. AddAttrToCtx adds metadata to the ctx which will be added to the error, if the context is set.

If 0 metadata will be included with the error, i.e both context is nil and meta is empty, the original error will be returned to avoid bloating the error chain.

Note that the slog.LogValuer output contains 2 keys by default, "msg" and "source", These can be changed via DefaultMsgSlogKey and DefaultSourceSlogKey. Duplicate keys are not supported.

func WrapAttrCtxAfter

func WrapAttrCtxAfter(ctx context.Context, errPtr *error, meta ...slog.Attr)

WrapAttrCtxAfter is WrapAttrCtx for usage with defer. Defer at the top of a function with a named return error variable to wrap any error returned from the function with your desired metadata. An example of a function that returns structured errors to slog with convenience functions to reduce boilerplate:

func DeleteDevice(ctx context.Context, tx sqlx.ExecerContext, id uint64) (err error) {
	defer errors.WrapAttrCtxAfter(ctx, &err, slog.Uint64("device_id", id))
	propQuery := `DELETE FROM device_properties WHERE device_id = ?`
	propsResult, err := tx.ExecContext(ctx, propQuery, id)
	if err != nil {
		return errors.Wrapf(err, "failed deleting device properties")
	}

	propsDeleted, err := propsResult.RowsAffected()
	if err != nil {
		return errors.Wrapf(err, "failed counting deleted device properties")
	}
	defer errors.WrapAttrCtxAfter(ctx, &err, slog.Uint64("deleted_props", propsDeleted))

	query := `DELETE FROM device WHERE id = ?`
	_,err = tx.ExecContext(ctx, query, id)
	return errors.Wrapf(err, "tx.Exec failed")
}

The output of slogging this function's failure with slog.Errorf("db error", "err", err):

2025/06/26 15:22:57 ERROR db error err.msg="errors.DeleteDevice tx.Exec failed" err.device_id=9 err.deleted_props=5 err.source=github.com/danlock/pkg/errors/ctx.go:76

Using defer WrapAttrCtxAfter throughout our code makes it more maintainable by adding metadata when it's available, only if the error exists. Consider using WrapAttrCtxAfter within any error returning function with a context.Context parameter.

func Wrapf

func Wrapf(err error, format string, a ...any) error

Wrapf wraps an error with the caller's package.func prepended. Similar to github.com/pkg/errors.Wrapf and unlike fmt.Errorf it returns nil if err is nil. If not wrapping an error from this Go package it also includes the file and line info of it's caller.

func WrapfWithSkip

func WrapfWithSkip(err error, skip int, format string, a ...any) error

WrapfWithSkip wraps an error with the caller's package.func prepended. Similar to github.com/pkg/errors.Wrapf and unlike fmt.Errorf it returns nil if err is nil. If not wrapping an error from this Go package it also includes the file and line info of it's caller. skip is the number of stack frames to skip before recording the function info from runtime.Callers.

Types

type AttrError

type AttrError interface {
	Attrs() iter.Seq[slog.Attr]
	LogValue() slog.Value
	Unwrap() error
	Error() string
}

AttrError is a structured error using slog.Attr for metadata, similar to log/slog. If printed with %+v it will also include the metadata, but by default only the error message is shown. The file:line information from the first error in the chain is also included under the DefaultSourceSlogKey. Implements slog.LogValuer which logs each slog.Attr in the entire error chain under a slog.GroupValue.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL