errors

package module
v1.2.4 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: MIT Imports: 8 Imported by: 0

README

errors

A Go library for working with errors that supports structured context.

Getting started

Add the dependency with:

go get github.com/sirkon/errors@latest

See a minimal setup and usage example in internal/example/main.go.

Features and goals

  • Errors are considered to be processes, not values. This means you should not compare two errors as you would not try to compare two processes and do not use errors.New and errors.Newf to create sentinel errors. There are errors.NewSentinel and errors.NewSentinelf for this.
  • Almost drop-in replacement for the standard errors package.
  • Avoid the inconsistency in the standard library where you use errors.New but fmt.Errorf.
  • Real first class error wrapping support with errors.Wrap and errors.Wrapf.
  • Structured context support, so you don’t have to log the same error at multiple layers just to add details — simply attach context to the error and the extra data will be rendered by default.
  • Optional inclusion of the file:line location where the error was created/handled. See loc.go.

Usage examples

// You can create errors and attach context to it.
return errors.New("unexpected name").Str("expected", expected).Str("actual", name)

// You can wrap and add context.
if err != nil {
return errors.Wrap(err, "do something").Int("int", intVal).Str("str", strVal)
}

// Sometimes text annotation doesn't make a sense, but some context info does.
if err != nil {
return errors.Just(err).Int("int", intVal)
}

Performance

The benchmark produced the following numbers:

Operation ns/op B/op Allocs/op
Wrap. 126.7 528 6
fmt.Errorf("…: %w") 252.3 296 9
Wrap with short context. 130.6 528 6
fmt.Errorf with text formatting matching that short context 306.9 512 8 (strange, why -1 ?)
errors.Wrap with large context 602.2 3898 11
fmt.Errorf with large text formatting 1214.0 2769 18

As you see, this library has both faster and doesn't degrade at the scale.

Now, pipeline benchmarking. We get an error, we annotate it, we log it. Four cases in here:

  1. Development mode logging with slog.
  2. Production mode logging with slog.
  3. We log at every step of stdlib's error processing to mimic this library' behavior.
  4. We just put everything in text format to deliver the context.

Here outputs look like

Dev.

{
  "time": "2026-02-21T23:24:48.261254+03:00",
  "level": "ERROR",
  "msg": "log error with tree structured context",
  "err": "check error: this is an error",
  "@err": {
    "NEW: this is an error": {
      "bytes": "AQID",
      "text-bytes": "Hello World!"
    },
    "WRAP: check error": {
      "count": 333,
      "is-wrap-layer": true
    },
    "CTX": {
      "pi": 3.141592653589793,
      "e": 2.718281828459045
    }
  }
}

Prod.

{
  "time": "2026-02-21T23:24:48.261709+03:00",
  "level": "ERROR",
  "msg": "log error with flat structured context",
  "err": "check error: this is an error",
  "@err": {
    "bytes": "AQID",
    "text-bytes": "Hello World!",
    "count": 333,
    "is-wrap-layer": true,
    "pi": 3.141592653589793,
    "e": 2.718281828459045
  }
}

fmt.Errorf with the logging at every step

{
  "time": "2026-02-21T23:34:53.317258+03:00",
  "level": "ERROR",
  "msg": "failed to do something 1",
  "err": "this is an error",
  "bytes": "AQID",
  "text-bytes": "Hello World!"
}
{
  "time": "2026-02-21T23:34:53.317293+03:00",
  "level": "ERROR",
  "msg": "failed to check error",
  "err": "this is an error",
  "count": 333,
  "is-wrap-layer": true
}
{
  "time": "2026-02-21T23:34:53.317299+03:00",
  "level": "ERROR",
  "msg": "got an error",
  "err": "check error: this is an error",
  "pi": 3.141592653589793,
  "e": 2.718281828459045
}

fmt.Errorf with text format.

{
  "time": "2026-02-21T23:29:56.447285+03:00",
  "level": "ERROR",
  "msg": "failed to do something 1",
  "err": "context pi[3.141592653589793] e[2.718281828459045]: check error count[333] is-wrap-layer[true]: this is an error bytes[[1 2 3]] text-bytes[Hello World!]"
}

We disabled location logging in both logger and this library (which makes it at every New, Wrap and Just).

Test ns/op
Dev 3266
Prod 2990
fmt.Errorf and multiple logging 7049
fmt.Errorf and text format 2611

As you can see, fmt.Errorf with text format is just 14% faster than Prod flat context solution being worse in every other way possible. Multiple logging is slow and just an antipattern.

Appendix.

This is how full Dev output looks like:

{
  "time": "2026-02-21T23:58:48.542593+03:00",
  "level": "ERROR",
  "msg": "log error with tree structured context",
  "err": "check error: this is an error",
  "@err": {
    "NEW: this is an error": {
      "@location": "/Users/d.cheremisov/Sources/mine/errors/internal/example/example.go:16",
      "bytes": "AQID",
      "text-bytes": "Hello World!"
    },
    "WRAP: check error": {
      "@location": "/Users/d.cheremisov/Sources/mine/errors/internal/example/example.go:19",
      "count": 333,
      "is-wrap-layer": true
    },
    "CTX": {
      "@location": "/Users/d.cheremisov/Sources/mine/errors/internal/example/example.go:22",
      "pi": 3.141592653589793,
      "e": 2.718281828459045
    }
  }
}

Documentation

Overview

Package awesome_errors provides contextful errors and slog automation to consume and show collected contexts.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target any) bool

As mirrors errors.As.

func AsType added in v1.0.0

func AsType[E error](err error) (E, bool)

AsType mirrors errors.AsType.

func DoNotInsertLocations added in v1.0.0

func DoNotInsertLocations()

DoNotInsertLocations disables the insertion of error handling positions. This is the default mode.

func InsertLocations added in v1.0.0

func InsertLocations()

InsertLocations enables the insertion of error handling positions.

WARNING!

Calculating positions is a very expensive operation, so it is highly discouraged to enable position capturing in production. Enable only during local debugging.

The recommended usage pattern is enabling when using "development" logging mode, with colored output and formatting intended for reading.

func Is

func Is(err error, target error) bool

Is mirrors errors.Is.

func Join added in v0.6.0

func Join(errs ...error) error

Join mirrors errors.Join.

func NewSentinel added in v1.2.2

func NewSentinel(msg string) error

NewSentinel creates static error that can be a sentinel.

func NewSentinelf added in v1.2.2

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

NewSentinelf same as NewSentinel, just with a format instead of a static message.

func SLogFlatContext added in v1.2.2

func SLogFlatContext(err *Error) []slog.Attr

func SLogTreeContext added in v1.2.2

func SLogTreeContext(err *Error) []slog.Attr

SLogTreeContext builds slog tree for logging on the place without ErrorContextConsumer indirect calls overhead.

Types

type Error added in v0.2.0

type Error struct {
	// contains filtered or unexported fields
}

func Just added in v0.5.0

func Just(err error) *Error

Just возвращает *Error позволяющий добавить контекст данной ошибке.

func New

func New(msg string) *Error

New creates new error with the given text.

func Newf

func Newf(format string, a ...any) *Error

Newf creates new error with the given format.

func Wrap

func Wrap(err error, msg string) *Error

Wrap annotates given error with the given message.

func Wrapf

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

Wrapf annotates given error with the given format.

func (*Error) Any added in v0.2.0

func (e *Error) Any(key string, value any) *Error

func (*Error) As added in v0.2.0

func (e *Error) As(target any) bool

As implements support for Is. Remember, *Error instances are ephemeral and cannot be targeted.

func (*Error) Bool added in v0.2.0

func (e *Error) Bool(key string, value bool) *Error

func (*Error) Bytes added in v1.0.0

func (e *Error) Bytes(key string, value []byte) *Error

func (*Error) Error added in v0.2.0

func (e *Error) Error() string
Example
package main

import (
	"fmt"
	"io"

	"github.com/sirkon/errors"
)

func main() {
	errs := []error{
		func() error {
			err := errors.New("new error")
			err = errors.Wrap(err, "wrap")
			err = errors.Wrapf(err, "wrap no %d", 2)
			return err
		}(),
		func() error {
			return errors.Wrap(io.EOF, "wrap")
		}(),
	}
	for _, err := range errs {
		fmt.Println(err)
	}

}
Output:

wrap no 2: wrap: new error
wrap: EOF

func (*Error) F32 added in v1.2.2

func (e *Error) F32(key string, value float32) *Error

func (*Error) F64 added in v1.2.2

func (e *Error) F64(key string, value float64) *Error

func (*Error) I8 added in v1.2.2

func (e *Error) I8(key string, value int8) *Error

func (*Error) I16 added in v1.2.2

func (e *Error) I16(key string, value int16) *Error

func (*Error) I32 added in v1.2.2

func (e *Error) I32(key string, value int32) *Error

func (*Error) I64 added in v1.2.2

func (e *Error) I64(key string, value int64) *Error

func (*Error) Int added in v0.2.0

func (e *Error) Int(key string, value int) *Error

func (*Error) Is added in v0.2.0

func (e *Error) Is(target error) bool

Is implements support for Is. Remember, *Error instances are ephemeral and cannot be targeted.

func (*Error) Stg added in v0.2.0

func (e *Error) Stg(key string, value fmt.Stringer) *Error

func (*Error) Str added in v0.2.0

func (e *Error) Str(key, value string) *Error

func (*Error) Strs added in v0.2.0

func (e *Error) Strs(key string, value []string) *Error

func (*Error) U8 added in v1.2.2

func (e *Error) U8(key string, value uint8) *Error

func (*Error) U16 added in v1.2.2

func (e *Error) U16(key string, value uint16) *Error

func (*Error) U32 added in v1.2.2

func (e *Error) U32(key string, value uint32) *Error

func (*Error) U64 added in v1.2.2

func (e *Error) U64(key string, value uint64) *Error

func (*Error) Uint added in v0.2.0

func (e *Error) Uint(key string, value uint) *Error

type ErrorContextBuilder added in v1.2.2

type ErrorContextBuilder interface {
	Bool(name string, value bool)
	Int64(name string, value int64)
	Uint64(name string, value uint64)
	Flt64(name string, value float64)
	Str(name string, value string)
	Any(name string, value any)
	Loc(position string)
	Finalize()
}

ErrorContextBuilder is the contract for dispatching context values per layer of processing.

type ErrorContextConsumer added in v0.2.0

type ErrorContextConsumer interface {
	New(msg string) ErrorContextBuilder
	Wrap(msg string) ErrorContextBuilder
	Just() ErrorContextBuilder
}

ErrorContextConsumer is the contract that must be implemented by the logging side to receive the structured values stored in the error.

type ErrorContextDeliverer added in v0.2.0

type ErrorContextDeliverer interface {
	Deliver(cons ErrorContextConsumer)
	Error() string
}

ErrorContextDeliverer is what is returned by the GetContextDeliverer function. Example of how to work with slog.

type slogConsumer struct{
   fields []any
}

func (c *slogConsumer) Int(name, value int) {
   c.fields = append(c.fields, slog.Int(name, value))
}

// And so on for the rest of the methods to implement errors.ErrorContextConsumer.

// Log logs at the Info level.
func Log(msg string, fields []slog.Attr) {
    var attrs []any
    for _, field := range fields {
        // Save all original fields.
        attrs = append(attrs, field)

        // Need to unwrap errors. For this, we look for fields that contain them.
        fv := field.Value.Any()
        e, ok := fv.(error)
        if !ok {
            continue
        }

        // v contains an error. Try to get context from it.
        dlr := errors.GetContextDeliverer(e)
        if dlr == nil {
            // This is not our error.
            continue
        }

        // Get the context and add the extracted fields.
        var errCtx slogConsumer{}
        dlr.Deliver(&errCtx)
        attrs = append(attrs, errCtx.fields...)
    }

    slog.Info(msg, attrs...)
}

A working example can be found in ./internal/example/main.go.

func GetContextDeliverer added in v0.2.0

func GetContextDeliverer(err error) ErrorContextDeliverer

GetContextDeliverer returns the structured-context deliverer for the given error.

Example
package main

import (
	"fmt"

	"github.com/sirkon/errors"
	"github.com/sirkon/errors/errorsctx"
)

func main() {
	err := errors.New("test error").Int("count", 12).Str("text", "Hello")
	err = errors.Wrap(err, "check").Uint("unsign", 333).Bytes("actually-text", []byte("World!"))
	err = errors.Just(err).Bool("this-is-the-last", true).Bytes("real-raw", []byte{1, 2, 3})

	dlvr := errors.GetContextDeliverer(err)
	var c errorsctx.Consumer
	dlvr.Deliver(&c)

	for _, layer := range c.Layers {
		fmt.Println(layer)
		for _, pair := range layer.Pairs {
			fmt.Printf("    %s: %v\n", pair.Key, pair.Value.Any())
		}
	}

}
Output:

NEW: test error
    count: 12
    text: Hello
WRAP: check
    unsign: 333
    actually-text: World!
CTX
    this-is-the-last: true
    real-raw: [1 2 3]

func MustGetContextDeliverer added in v1.2.2

func MustGetContextDeliverer(err *Error) ErrorContextDeliverer

MustGetContextDeliverer guaranteed delivery on the instance of Error istelf.

Directories

Path Synopsis
Package errorsctx provides ready to use generic consumer and slog handlers.
Package errorsctx provides ready to use generic consumer and slog handlers.
internal
example command
Package main provides an example of configuration, usage, and results of working with the library.
Package main provides an example of configuration, usage, and results of working with the library.

Jump to

Keyboard shortcuts

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