Skip to main content

Overview

errorc — structured context for Go errors

errorc is a small Go library that augments the standard error type with structured context while keeping compatibility with errors.Is and errors.As.

It helps you create errors like:

  • invalid input, field1: value1, field2: value2
  • query failed, retries: 3, cached: false
  • operation failed, cause: disk full
  • storage: read_failed

The API is intentionally small:

  • errorc.New(...) creates a new error.
  • errorc.With(...) wraps an existing error with context fields.
  • errorc.String(...), errorc.Int(...), errorc.Bool(...), and errorc.Error(...) create fields.
  • errorc.WithNamespace(...), errorc.Namespace, and errorc.ErrorFactory(...) help build namespaced error identifiers.

For structured field keys such as auth.user.id or http.request.id, use the companion keys library.


Quick start

package main

import (
"errors"
"fmt"

"github.com/ygrebnov/errorc"
"github.com/ygrebnov/keys"
)

func main() {
ErrInvalidInput := errorc.New("invalid input")
userKey := keys.Factory(keys.WithSegments("user"))

err := errorc.With(
ErrInvalidInput,
errorc.String(userKey("id"), "123"),
errorc.Bool("cached", false),
)

if errors.Is(err, ErrInvalidInput) {
fmt.Println(err)
}

// Output:
// invalid input, user.id: 123, cached: false
}

Why use it?

  • Preserves error identity for errors.Is and errors.As.
  • Adds structured fields without giving up plain error interoperability.
  • Supports primitive field helpers for strings, ints, bools, and nested errors.
  • Pairs naturally with keys for hierarchical field names.

Core concepts

With

Use errorc.With(err, fields...) to attach structured context to an existing error.

err := errorc.With(
errorc.New("query failed"),
errorc.String("query", "select * from users"),
errorc.Int("retries", 3),
)
fmt.Println(err) // query failed, query: select * from users, retries: 3

String, Int, Bool, and Error

Use the field helpers for common value types.

cause := errors.New("disk full")
err := errorc.With(
errorc.New("operation failed"),
errorc.Bool("retryable", false),
errorc.Error("cause", cause),
)
fmt.Println(err) // operation failed, retryable: false, cause: disk full

Sentinel errors

Because errorc.With(...) wraps the original error, you can still use errors.Is.

ErrInvalidInput := errorc.New("invalid input")
err := errorc.With(ErrInvalidInput, errorc.String("field", "email"))

fmt.Println(errors.Is(err, ErrInvalidInput)) // true

Typed errors

Typed errors remain discoverable through errors.As.

type ValidationError struct{ Message string }

func (e *ValidationError) Error() string { return e.Message }

err := errorc.With(&ValidationError{"invalid input"}, errorc.String("field", "email"))

var ve *ValidationError
fmt.Println(errors.As(err, &ve)) // true

Namespaced errors

Use namespaces when you want simple identifiers like storage: read_failed.

storageErr := errorc.ErrorFactory("storage")
fmt.Println(storageErr("read_failed")) // storage: read_failed

For dotted structured keys such as storage.read.failed, prefer the keys library.


Structured keys with keys

errorc field helpers are generic over any type whose underlying type is string, which means they accept keys.Key values directly.

userIDKey := keys.New("id", keys.WithSegments("user"))
traceKey := keys.New("trace_id", keys.WithSegments("request"))

err := errorc.With(
errorc.New("invalid input"),
errorc.String(userIDKey, "123"),
errorc.String(traceKey, "abc-xyz"),
)

fmt.Println(err)
// invalid input, user.id: 123, request.trace_id: abc-xyz

If your project uses hierarchical key names consistently across logs, metrics, and errors, see the keys overview and keys examples.


Compared to fmt.Errorf

The errorc.With function behaves similarly to fmt.Errorf for adding context, but performs significantly faster in the project benchmarks:

BenchmarkWith-8 53965288 21.81 ns/op
BenchmarkFmtErrorf-8 7401583 186.7 ns/op

Notes

  • errorc.With(nil, ...) returns nil.
  • Nil fields returned by helpers such as errorc.Error("cause", nil) are ignored.
  • Empty field keys print only the value.
  • The library requires Go 1.22 or later.

See Installation, Examples, and the companion keys project for more.