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.