zerr

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Dec 4, 2025 License: MIT Imports: 8 Imported by: 0

README

zerr - High-Performance Error Handling Library

zerr is a production-ready, high-performance Go error handling library that provides modern, idiomatic error wrapping with lazy stack traces, deduplication, and structured metadata.

Features

  • Lazy & Deduplicated Stack Traces: Capture stack traces lazily using runtime.Callers and deduplicate them using a global cache
  • Efficient & Deduplicated Stack Traces: Capture stack traces efficiently using pooled buffers and deduplicate them using a global cache, deferring expensive symbol resolution until formatting
  • Low-Overhead Wrapping: Optimized happy path with minimal allocation overhead for error wrapping
  • Structured Metadata: Attach typed key-value pairs to errors efficiently
  • Native slog Integration: Automatic structured logging with slog.LogValuer implementation
  • Goroutine Safety: Safe recovery from panics in goroutines with Defer()
  • Typed Nil Issue Fix: Fixed the common Go bug where nil pointers with types aren't truly nil

Requirements

  • Go 1.25 or later

Installation

go get go.trai.ch/zerr

Usage

API Note: The New and Wrap functions return error to prevent the typed nil issue. You can use the global helper functions zerr.With and zerr.Stack to add context to any error without manual type assertion.

Basic Error Creation
import "go.trai.ch/zerr"

// Create a new error
err := zerr.New("something went wrong")

// Wrap an existing error
err = zerr.Wrap(err, "failed to process request")
Adding Metadata

You can add metadata using the global helper (works with any error) or by method chaining (requires casting).

// Option 1: Use the global helper (easiest)
// Automatically upgrades standard errors to zerr.Error
err := zerr.New("database error")
err = zerr.With(err, "table", "users")

// Option 2: Method chaining (fastest for multiple fields)
// Requires type assertion since New() returns standard error
if zerrErr, ok := err.(*zerr.Error); ok {
    err = zerrErr.With("operation", "insert").
        With("user_id", 12345)
}
Accessing Metadata

You can access the structured metadata attached to errors using the Metadata() method:

// Create an error with metadata
err := zerr.New("database error")
err = zerr.With(err, "table", "users")
err = zerr.With(err, "operation", "insert")
err = zerr.With(err, "user_id", 12345)

// Access metadata (requires type assertion to *zerr.Error)
if zerrErr, ok := err.(*zerr.Error); ok {
    metadata := zerrErr.Metadata()
    // metadata is map[string]any{"table": "users", "operation": "insert", "user_id": 12345}
    
    // Access individual values
    if table, exists := metadata["table"]; exists {
        fmt.Printf("Table: %v\n", table)
    }
}

The Metadata() method returns a copy of the error's metadata as a map[string]any. It returns an empty map if there is no metadata attached to the error. The returned map is a copy, ensuring that modifications to it do not affect the internal state of the error.

Stack Traces

Capture stack traces easily using the global Stack helper.

// Capture stack trace lazily
err := zerr.New("critical failure")
err = zerr.Stack(err)

// Works with standard errors too (upgrades them)
stdErr := errors.New("standard Go error")
err = zerr.Stack(stdErr)
Logging with slog
import "log/slog"

// Log errors with structured fields
zerr.Log(context.Background(), slog.Default(), err)

// Errors automatically format themselves when logged
logger.Error("operation failed", "error", err)
Goroutine Safety
func backgroundTask() {
    defer zerr.Defer(func(err error) {
        // Handle recovered errors
        zerr.Log(context.Background(), slog.Default(), err)
    })

    // Potentially panicking code
    panic("something went wrong")
}

Performance

Benchmarks run on Apple M4 Pro (Go 1.25) demonstrate the efficiency of the deduplication engine.

The single allocation in New and Wrap ensures type safety (preventing typed nil bugs), while the stack trace machinery remains zero-allocation for cached traces.

BenchmarkNew-14                 93238076                12.55 ns/op           64 B/op          1 allocs/op
BenchmarkWrap-14                92826571                12.76 ns/op           64 B/op          1 allocs/op
BenchmarkWrapWithStack-14        7189615               169.5 ns/op           128 B/op          2 allocs/op
BenchmarkWithMetadata-14        15186404                78.50 ns/op          200 B/op          4 allocs/op
BenchmarkErrorFormatting-14     752197629                1.591 ns/op           0 B/op          0 allocs/op

Note: BenchmarkWrapWithStack incurring only 1 allocation (for the error struct itself) demonstrates the effectiveness of the global stack cache. Once a specific stack trace is captured, adding it to an error incurs no additional memory allocation overhead beyond the error wrapper.

The happy path (New/Wrap) is highly optimized. Heavy operations like stack tracing use internal pooling and deduplication to eliminate GC pressure in hot paths.

License

MIT

Documentation

Overview

Package zerr provides utilities for safe error handling in goroutines.

Package zerr provides slog integration for structured error logging.

Package zerr provides stack trace utilities for the error handling library.

Package zerr provides a high-performance error handling library with lazy stack traces, deduplication, and structured metadata.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Defer

func Defer(handler func(error))

Defer recovers from panics in goroutines and converts them to errors.

func Log

func Log(ctx context.Context, logger *slog.Logger, err error)

Log logs an error using the provided slog.Logger with structured fields.

func New

func New(message string) error

New creates a new error with the given message.

Example

Keep Examples...

err := New("something went wrong")
fmt.Println(err.Error())
Output:

something went wrong

func With

func With(err error, key string, value any) error

With attaches a key-value pair to an error. If err is already a *Error, it attaches the metadata directly. If err is a standard error, it wraps it to allow attaching metadata.

Example
testErr := New("database error")
err, _ := testErr.(*Error)
err = err.With("table", "users").With("operation", "insert")
fmt.Println(err.Error())
Output:

database error

func WithStack

func WithStack(err error) error

Stack captures the stack trace for the error. If err is already a *Error, it attaches the stack trace directly. If err is a standard error, it wraps it to capture the stack trace.

func Wrap

func Wrap(err error, message string) error

Wrap wraps an existing error with an additional message. If err is nil, Wrap returns nil.

Example
cause := errors.New("network timeout")
err := Wrap(cause, "failed to fetch data")
fmt.Println(err.Error())
Output:

failed to fetch data: network timeout

Types

type Error

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

Error represents an error with optional stack trace and metadata.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) Format

func (e *Error) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface to allow for printing stack traces.

func (*Error) LogValue

func (e *Error) LogValue() slog.Value

LogValue implements slog.LogValuer for automatic formatting when logged.

func (*Error) Metadata

func (e *Error) Metadata() map[string]any

Metadata returns a copy of the error's metadata as a map. Returns an empty map if there is no metadata attached to the error.

func (*Error) StackTrace

func (e *Error) StackTrace() string

StackTrace returns a formatted stack trace string. Uses lazy formatting - the stack trace is only formatted when this method is called.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap implements the unwrap interface for error chaining.

func (*Error) With

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

With attaches a key-value pair to the error as metadata.

func (*Error) WithStack

func (e *Error) WithStack() *Error

WithStack captures a stack trace for this error.

Jump to

Keyboard shortcuts

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