errors

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2025 License: MIT Imports: 10 Imported by: 0

README

stackprune/errors

A modern, lightweight error handling library for Go — focused on:

  • ✅ Clean, single-stack trace errors (no duplication)
  • ✅ Full compatibility with Go's errors package
  • ✅ Seamless structured logging via slog
  • ✅ Panic recovery helpers with preserved stack
  • ✅ No external dependencies

Why stackprune/errors?

Go's standard errors and popular pkg/errors often capture new stack traces on each wrap, leading to redundant, noisy stack dumps.

stackprune/errors captures the stack once at the origin and preserves it across all wraps. No redundant stacks. Clean, minimal, highly debuggable output.


Installation

go get github.com/stackprune/errors

Quick Start

import "github.com/stackprune/errors"

// Create error with stack trace
err := errors.New("failed to connect")

// Wrap with additional context (preserves stack)
err = errors.Wrap(err, "while starting server")

// Format errors
err = errors.Errorf("user %d not found", userID)

// Add stack to 3rd party errors
err = errors.WithStack(io.EOF)

// Print with full stack trace
fmt.Printf("%+v\n", err)

Key API Summary

Function Purpose
New() Create error, capture stack once
Wrap() Add context, no new stack
WithStack() Attach stack if missing
Errorf() Formatted error with stack
Join() Combine multiple errors
RecoverError() Build error from panic recovery
NewWithCallers() Build error from external program counters

Full API: Go Reference


Panic Recovery

Capture panics with full stack trace easily:

func safeOperation() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = errors.RecoverError(fmt.Sprintf("panic: %v", r))
		}
	}()
	panic("something failed")
}

For advanced control (external call stacks):

func advancedRecovery() (err error) {
	defer func() {
		if r := recover(); r != nil {
			var pcs [32]uintptr
			n := runtime.Callers(0, pcs[:])
			err = errors.NewWithCallers(fmt.Sprint(r), pcs[:n])
		}
	}()
	panic("something failed")
}

Multiple Error Handling

var errs []error

if err := op1(); err != nil {
	errs = append(errs, errors.Wrap(err, "op1 failed"))
}
if err := op2(); err != nil {
	errs = append(errs, errors.Wrap(err, "op2 failed"))
}

return errors.Join(errs...)

Structured Logging (slog)

Seamless integration with Go's slog:

err := errors.New("database failure")
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Error("operation failed", slog.Any("error", err))

Output example:

{
  "level": "ERROR",
  "msg": "operation failed",
  "error": {
    "message": "database failure",
    "kind": "*errors.Error",
    "stack": [
      "connectDB at db.go:42",
      "main at main.go:10"
    ]
  }
}
Customizing stack output
errors.SetLogOptions(errors.LogOptions{
	StackFormat: errors.StackFormatObjectArray,
})

Produces:

"stack": [
  {"file": "db.go", "function": "connectDB", "line": 42},
  {"file": "main.go", "function": "main", "line": 10}
]

Debug Output

Use %+v to print full error with stack trace:

fmt.Printf("%+v\n", err)

Example output:

user creation failed: failed to insert
main.repositoryInsertUser
    /app/main.go:133
main.usecaseCreateUser
    /app/main.go:125
main.handleCreateUser
    /app/main.go:117
main.main
    /app/main.go:139

Requirements

Go 1.22+
Zero external dependencies

License

MIT License

Documentation

Overview

Package errors provides a modern, lightweight alternative to pkg/errors, capturing a single stack trace at error creation time and preserving it through all wraps. It avoids redundant stack traces while maintaining compatibility with Go 1.13+ errors.Is / errors.As / errors.Unwrap.

The API includes `New`, `Wrap`, `Errorf`, `WithStack`, and `Join`, offering familiar ergonomics without the noise of `WithMessage` or similar.

The *Error type also implements slog.LogValuer for structured logging.

Example (SlogStructuredLogging)

Example_slogStructuredLogging demonstrates how to use slog for structured logging

package main

import (
	"log/slog"
	"os"

	"github.com/stackprune/errors"
)

func main() {
	err := errors.WithStack(errors.New("missing config"))

	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
	logger.Error("initialization failed", slog.Any("error", err))

	// Example output:
	// {
	//   "time": "2025-06-10T10:01:44.693101023+09:00",
	//   "level": "ERROR",
	//   "msg": "initialization failed",
	//   "error": {
	//     "message": "missing config",
	//     "kind": "*errors.Error",
	//     "stack": [
	//       "loadConfig at config.go:42",
	//       "main at main.go:10"
	//     ]
	//   }
	// }
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target any) bool

As reports whether any error in err’s chain matches target, same as errors.As.

func Errorf

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

Errorf formats a message and returns an error with a stack trace.

Example

ExampleErrorf demonstrates formatted error creation.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	userID := 42
	err := errors.Errorf("user %d not found", userID)
	fmt.Println(err.Error())
}
Output:

user 42 not found

func Is

func Is(err, target error) bool

Is reports whether err is or wraps target, same as errors.Is.

func Join

func Join(errs ...error) error

Join combines multiple errors into a single error value, ignoring nils.

Example

ExampleJoin demonstrates joining multiple errors.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	err1 := errors.New("first error")
	err2 := errors.New("second error")
	err3 := stderrors.New("third error")

	joinedErr := errors.Join(err1, err2, err3)
	fmt.Println(joinedErr.Error())
}
Output:

first error
second error
third error
Example (Single)

ExampleJoin_single demonstrates that joining a single error returns the original error.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	originalErr := errors.New("single error")
	joinedErr := errors.Join(originalErr)

	fmt.Printf("Original: %s\n", originalErr)
	fmt.Printf("Joined: %s\n", joinedErr)

}
Output:

Original: single error
Joined: single error
Example (WithNil)

ExampleJoin_withNil demonstrates that nil errors are ignored in Join.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	err1 := errors.New("first error")
	err2 := errors.New("second error")

	joinedErr := errors.Join(err1, nil, err2, nil)
	fmt.Println(joinedErr.Error())
}
Output:

first error
second error

func New

func New(message string) error

New creates an error with a message. It initializes an Error struct with the provided message.

Example

ExampleNew demonstrates basic error creation with stack trace.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	err := errors.New("something went wrong")
	fmt.Println(err.Error())
}
Output:

something went wrong

func NewWithCallers added in v1.1.0

func NewWithCallers(message string, programCounters []uintptr) error

NewWithCallers creates an error with a message and provided program counters. It allows attaching an external stack trace, e.g., from recover handlers.

func RecoverError added in v1.1.0

func RecoverError(message string) error

RecoverError creates an error with a message and stack trace. This is a convenience function for defer/recover patterns where you want to capture the current stack trace.

Example

ExampleRecoverError demonstrates error creation from panic recovery.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	safeFunc := func() (err error) {
		defer func() {
			if r := recover(); r != nil {
				err = errors.RecoverError(fmt.Sprintf("database operation failed: %v", r))
			}
		}()

		// This will panic
		panic("connection timeout")
	}

	err := safeFunc()
	fmt.Printf("%+v\n", err)

	// Example Output:
	// database operation failed: connection timeout
	// runtime.gopanic
	// 	/usr/local/go/src/runtime/panic.go:770
	// github.com/stackprune/errors_test.ExampleRecoverError.func1
	// 	/app/example_test.go:208
	// github.com/stackprune/errors_test.ExampleRecoverError
	// 	/app/example_test.go:211
}

func SetLogOptions added in v1.0.0

func SetLogOptions(options LogOptions)

SetLogOptions updates the global LogOptions used for logging *Error values with slog. This affects all future calls to *Error.LogValue. This function is safe for concurrent use.

func Unwrap

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if any.

Example

ExampleUnwrap demonstrates unwrapping errors.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	baseErr := errors.New("base error")
	wrappedErr := errors.Wrap(baseErr, "wrapped error")

	unwrapped := errors.Unwrap(wrappedErr)
	fmt.Println(unwrapped.Error())

}
Output:

base error

func WithStack

func WithStack(err error) error

WithStack adds a stack trace to the error, if not already present.

Example

ExampleWithStack demonstrates adding stack trace to standard errors.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	standardErr := stderrors.New("standard library error")
	stackErr := errors.WithStack(standardErr)
	fmt.Println(stackErr.Error())
}
Output:

standard library error

func Wrap

func Wrap(e error, message string) error

Wrap wraps the given error with a message, preserving the original stack trace.

Example

ExampleWrap demonstrates wrapping an existing error.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	originalErr := stderrors.New("connection failed")
	wrappedErr := errors.Wrap(originalErr, "database access failed")
	fmt.Println(wrappedErr.Error())
}
Output:

database access failed: connection failed
Example (NetworkError)

ExampleWrap_networkError demonstrates wrapping real-world errors like network timeouts.

package main

import (
	"fmt"
	"io"

	"github.com/stackprune/errors"
)

func main() {
	// Simulate a network error (like io.ErrUnexpectedEOF)
	networkErr := io.ErrUnexpectedEOF

	// Wrap with context
	connectionErr := errors.Wrap(networkErr, "network connection lost")
	serviceErr := errors.Wrapf(connectionErr, "failed to fetch data from %s", "api.example.com")

	fmt.Println(serviceErr.Error())

}
Output:

failed to fetch data from api.example.com: network connection lost: unexpected EOF

func Wrapf

func Wrapf(e error, format string, args ...any) error

Wrapf wraps the error with a formatted message, similar to fmt.Sprintf.

Example

ExampleWrapf demonstrates formatted wrapping of an error.

package main

import (
	stderrors "errors"
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	originalErr := stderrors.New("timeout")
	tableName := "users"
	wrappedErr := errors.Wrapf(originalErr, "failed to query table %s", tableName)
	fmt.Println(wrappedErr.Error())
}
Output:

failed to query table users: timeout

Types

type Error

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

Error represents an error with optional stack trace and cause. Implements standard interfaces.

func (*Error) Cause

func (e *Error) Cause() error

Cause returns the underlying error.

func (*Error) Error

func (e *Error) Error() string

Error returns the error message with wrapped messages joined by ": ".

func (*Error) Format

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

Format implements fmt.Formatter. Use %+v for full stack trace.

Example

ExampleError_Format demonstrates different formatting options.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func main() {
	err := errors.New("example error")

	// Simple string format
	fmt.Printf("%%s: %s\n", err)

	// Quoted format
	fmt.Printf("%%q: %q\n", err)

	// Simple verbose format
	fmt.Printf("%%v: %v\n", err)

}
Output:

%s: example error
%q: "example error"
%v: example error
Example (StackTrace)

ExampleError_Format_stackTrace demonstrates stack trace formatting with %+v. across handler → usecase → repository layers using Wrap and WithStack.

package main

import (
	"fmt"

	"github.com/stackprune/errors"
)

func handlerCreateUser() error {
	if err := usecaseCreateUser(); err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func usecaseCreateUser() error {
	if err := repositoryInsertUser(); err != nil {
		return errors.Wrap(err, "user creation failed")
	}

	return nil
}

func repositoryInsertUser() error {
	return errors.New("failed to insert user into database")
}

func main() {
	err := handlerCreateUser()

	fmt.Printf("Error: %+v\n", err)

	// Example output:
	// Error: user creation failed: failed to insert user into database
	// github.com/stackprune/errors_test.repositoryInsertUser
	// 	/app/example_test.go:133
	// github.com/stackprune/errors_test.usecaseCreateUser
	// 	/app/example_test.go:125
	// github.com/stackprune/errors_test.handlerCreateUser
	// 	/app/example_test.go:117
	// github.com/stackprune/errors_test.ExampleError_Format_stackTrace
	// 	/app/example_test.go:139
}

func (*Error) LogValue added in v1.0.0

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

LogValue implements slog.LogValuer for *Error. It returns a structured slog.Value containing the error kind, message, and stack trace. Stack trace formatting is determined by the global LogOptions.

func (*Error) ProgramCounters added in v1.1.0

func (e *Error) ProgramCounters() []uintptr

ProgramCounters returns the raw program counters (for testing).

func (*Error) Stacks

func (e *Error) Stacks() []Stack

Stacks returns the stack trace.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause error for compatibility with errors.Is and errors.As.

type JoinError

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

JoinError represents a composed error returned by Join. Supports Is and As via Unwrap.

func (*JoinError) Error

func (j *JoinError) Error() string

Error returns the concatenated error messages from all errors. Multiple errors are joined with newlines.

func (*JoinError) Format

func (j *JoinError) Format(state fmt.State, verb rune)

Format implements fmt.Formatter for JoinError. Use %+v to display stack traces for the first Error type only.

func (*JoinError) Unwrap

func (j *JoinError) Unwrap() []error

Unwrap returns the slice of errors for compatibility with errors.Is and errors.As.

type LogOptions added in v1.0.0

type LogOptions struct {
	MessageKey  string
	KindKey     string
	StackKey    string
	StackFormat StackFormat
}

LogOptions defines how *Error values are serialized for slog structured logging. It allows customization of key names and stack trace formatting.

func GetLogOptions added in v1.0.0

func GetLogOptions() LogOptions

GetLogOptions returns the current global LogOptions. If not explicitly set, default values are used. This function is safe for concurrent use.

type Stack

type Stack struct {
	File           string
	LineNumber     int
	FuncName       string
	ProgramCounter uintptr
}

Stack captures a single stack frame, including file, line, and function.

func NewStack

func NewStack(programCounter uintptr) Stack

NewStack constructs a Stack from a raw program counter using runtime metadata.

type StackFormat added in v1.0.0

type StackFormat int

StackFormat specifies the output format of stack traces in logs.

const (
	// StackFormatStringArray renders the stack trace as an array of strings.
	StackFormatStringArray StackFormat = iota

	// StackFormatObjectArray renders the stack trace as an array of structured objects.
	StackFormatObjectArray
)

Jump to

Keyboard shortcuts

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