ewrap

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2025 License: MIT Imports: 12 Imported by: 8

README

ewrap

Go Docs Go Report Card Go Reference GitHub Sponsors

A sophisticated, configurable error wrapper for Go applications that provides comprehensive error handling capabilities with a focus on performance and flexibility.

Core Features

  • Stack Traces: Automatically captures and filters stack traces for meaningful error debugging
  • Error Wrapping: Maintains error chains while preserving context through the entire chain
  • Metadata Attachment: Attach and manage arbitrary key-value pairs to errors
  • Logging Integration: Flexible logger interface supporting major logging frameworks (logrus, zap, zerolog)
  • Error Categorization: Built-in error types and severity levels for better error handling
  • Circuit Breaker Pattern: Protect your systems from cascading failures
  • Efficient Error Grouping: Pool-based error group management for high-performance scenarios
  • Context Preservation: Rich error context including request IDs, user information, and operation details
  • Thread-Safe Operations: Safe for concurrent use in all operations
  • Format Options: JSON and YAML output support with customizable formatting
  • Go 1.13+ Compatible: Full support for errors.Is, errors.As, and error chains

Installation

go get github.com/hyp3rd/ewrap

Documentation

ewrap has plenty of features with an exhaustive documentation, browse it here.

Usage Examples

Basic Error Handling

Create and wrap errors with context:

// Create a new error
err := ewrap.New("database connection failed")

// Wrap an existing error with context
if err != nil {
    return ewrap.Wrap(err, "failed to process request")
}

err = ewrap.Newf("failed to process request id: %v", requestID)
Advanced Error Context

Add rich context and metadata to errors:

err := ewrap.New("operation failed",
    ewrap.WithContext(ctx, ewrap.ErrorTypeDatabase, ewrap.SeverityCritical),
    ewrap.WithLogger(logger)).
    WithMetadata("query", "SELECT * FROM users").
    WithMetadata("retry_count", 3)

// Log the error with all context
err.Log()
Error Groups with Pooling

Use error groups efficiently in high-throughput scenarios:

// Create an error group pool with initial capacity
pool := ewrap.NewErrorGroupPool(4)

// Get an error group from the pool
eg := pool.Get()
defer eg.Release()  // Return to pool when done

// Add errors as needed
eg.Add(err1)
eg.Add(err2)

if eg.HasErrors() {
    return eg.Error()
}
Circuit Breaker Pattern

Protect your system from cascading failures:

// Create a circuit breaker for database operations
cb := ewrap.NewCircuitBreaker("database", 3, time.Minute)

if cb.CanExecute() {
    if err := performDatabaseOperation(); err != nil {
        cb.RecordFailure()
        return ewrap.Wrap(err, "database operation failed",
            ewrap.WithContext(ctx, ewrap.ErrorTypeDatabase, ewrap.SeverityCritical))
    }
    cb.RecordSuccess()
}
Complete Example

Here's a comprehensive example combining multiple features:

func processOrder(ctx context.Context, orderID string) error {
    // Get an error group from the pool
    pool := ewrap.NewErrorGroupPool(4)
    eg := pool.Get()
    defer eg.Release()

    // Create a circuit breaker for database operations
    cb := ewrap.NewCircuitBreaker("database", 3, time.Minute)

    // Validate order
    if err := validateOrderID(orderID); err != nil {
        eg.Add(ewrap.Wrap(err, "invalid order ID",
            ewrap.WithContext(ctx, ewrap.ErrorTypeValidation, ewrap.SeverityError)))
    }

    if !eg.HasErrors() && cb.CanExecute() {
        if err := saveToDatabase(orderID); err != nil {
            cb.RecordFailure()
            return ewrap.Wrap(err, "database operation failed",
                ewrap.WithContext(ctx, ewrap.ErrorTypeDatabase, ewrap.SeverityCritical))
        }
        cb.RecordSuccess()
    }

    return eg.Error()
}

Error Types and Severity

The package provides pre-defined error types and severity levels:

// Error Types
ErrorTypeValidation    // Input validation failures
ErrorTypeNotFound      // Resource not found
ErrorTypePermission    // Authorization/authentication failures
ErrorTypeDatabase      // Database operation failures
ErrorTypeNetwork       // Network-related failures
ErrorTypeConfiguration // Configuration issues
ErrorTypeInternal      // Internal system errors
ErrorTypeExternal      // External service errors

// Severity Levels
SeverityInfo      // Informational messages
SeverityWarning   // Warning conditions
SeverityError     // Error conditions
SeverityCritical  // Critical failures

Logging Integration

Implement the Logger interface to integrate with your logging system:

type Logger interface {
    Error(msg string, keysAndValues ...any)
    Debug(msg string, keysAndValues ...any)
    Info(msg string, keysAndValues ...any)
}

Built-in adapters are provided for popular logging frameworks:

// Zap logger
zapLogger, _ := zap.NewProduction()
err := ewrap.New("error occurred",
    ewrap.WithLogger(adapters.NewZapAdapter(zapLogger)))

// Logrus logger
logrusLogger := logrus.New()
err := ewrap.New("error occurred",
    ewrap.WithLogger(adapters.NewLogrusAdapter(logrusLogger)))

// Zerolog logger
zerologLogger := zerolog.New(os.Stdout)
err := ewrap.New("error occurred",
    ewrap.WithLogger(adapters.NewZerologAdapter(zerologLogger)))

Error Formatting

Convert errors to structured formats:

// Convert to JSON
jsonStr, _ := err.ToJSON(
    ewrap.WithTimestampFormat(time.RFC3339),
    ewrap.WithStackTrace(true))

// Convert to YAML
yamlStr, _ := err.ToYAML(
    ewrap.WithStackTrace(true))

Performance Considerations

The package is designed with performance in mind:

  • Error groups use sync.Pool for efficient memory usage
  • Minimal allocations in hot paths
  • Thread-safe operations with low contention
  • Pre-allocated buffers for string operations
  • Efficient stack trace capture and filtering

Development Setup

  1. Clone this repository:

    git clone https://github.com/hyp3rd/ewrap.git
    
  2. Install VS Code Extensions Recommended (optional):

    {
      "recommendations": [
        "github.vscode-github-actions",
        "golang.go",
        "ms-vscode.makefile-tools",
        "esbenp.prettier-vscode",
        "pbkit.vscode-pbkit",
        "trunk.io",
        "streetsidesoftware.code-spell-checker",
        "ms-azuretools.vscode-docker",
        "eamodio.gitlens"
      ]
    }
    
    1. Install Golang.

    2. Install GitVersion.

    3. Install Make, follow the procedure for your OS.

    4. Set up the toolchain:

      make prepare-toolchain
      
    5. Initialize pre-commit (strongly recommended to create a virtual env, using for instance PyEnv) and its hooks:

       pip install pre-commit
       pre-commit install
       pre-commit install-hooks
    

Project Structure

├── internal/ # Private code
│   └── logger/ # Application specific code
├── pkg/ # Public libraries)
├── scripts/ # Scripts for development
├── test/ # Additional test files
└── docs/ # Documentation

Best Practices

  • Follow the Go Code Review Comments
  • Run golangci-lint before committing code
  • Ensure the pre-commit hooks pass
  • Write tests for new functionality
  • Keep packages small and focused
  • Use meaningful package names
  • Document exported functions and types

Available Make Commands

  • make test: Run tests.
  • make benchmark: Run benchmark tests.
  • make update-deps: Update all dependencies in the project.
  • make prepare-toolchain: Install all tools required to build the project.
  • make lint: Run the staticcheck and golangci-lint static analysis tools on all packages in the project.
  • make run: Build and run the application in Docker.

License

MIT License

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

Refer to CONTRIBUTING for more information.

Author

I'm a surfer, and a software architect with 15 years of experience designing highly available distributed production systems and developing cloud-native apps in public and private clouds. Feel free to connect with me on LinkedIn.

LinkedIn

Documentation

Overview

Package ewrap provides enhanced error handling capabilities with stack traces, error wrapping, custom error types, and logging integration.

Package ewrap provides enhanced error handling capabilities

Package ewrap provides enhanced error handling capabilities

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CaptureStack

func CaptureStack() []uintptr

CaptureStack captures the current stack trace.

Types

type CircuitBreaker

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

CircuitBreaker implements the circuit breaker pattern for error handling.

func NewCircuitBreaker

func NewCircuitBreaker(name string, maxFailures int, timeout time.Duration) *CircuitBreaker

NewCircuitBreaker creates a new circuit breaker.

func (*CircuitBreaker) CanExecute

func (cb *CircuitBreaker) CanExecute() bool

CanExecute checks if the operation can be executed.

func (*CircuitBreaker) OnStateChange

func (cb *CircuitBreaker) OnStateChange(callback func(name string, from, to CircuitState))

OnStateChange sets a callback for state changes.

func (*CircuitBreaker) RecordFailure

func (cb *CircuitBreaker) RecordFailure()

RecordFailure records a failure and potentially opens the circuit.

func (*CircuitBreaker) RecordSuccess

func (cb *CircuitBreaker) RecordSuccess()

RecordSuccess records a success and potentially closes the circuit.

type CircuitState

type CircuitState int

CircuitState represents the state of a circuit breaker.

const (
	// CircuitClosed indicates normal operation.
	CircuitClosed CircuitState = iota
	// CircuitOpen indicates the circuit is broken.
	CircuitOpen
	// CircuitHalfOpen indicates the circuit is testing recovery.
	CircuitHalfOpen
)

type Error

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

Error represents a custom error type with stack trace and metadata.

Example (WithLogger)
logger := newTestLogger()
err := New("database connection failed", WithLogger(logger)).
	WithMetadata("retry_count", 3).
	WithMetadata("last_error", "connection timeout")

err.Log() // This will use the configured logger

// In real usage, you might use your preferred logging framework

func New

func New(msg string, opts ...Option) *Error

New creates a new Error with a stack trace and applies the provided options.

func Newf

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

Newf creates a new Error with a formatted message and applies the provided options.

func Wrap

func Wrap(err error, msg string, opts ...Option) *Error

Wrap wraps an existing error with additional context and stack trace.

func Wrapf

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

Wrapf wraps an error with a formatted message.

func (*Error) CanRetry

func (e *Error) CanRetry() bool

CanRetry checks if the error can be retried.

func (*Error) Cause

func (e *Error) Cause() error

Cause returns the underlying cause of the error.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) GetMetadata

func (e *Error) GetMetadata(key string) (any, bool)

GetMetadata retrieves metadata from the error.

func (*Error) IncrementRetry

func (e *Error) IncrementRetry()

IncrementRetry increments the retry counter.

func (*Error) Is

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

Is reports whether target matches err in the error chain.

func (*Error) Log

func (e *Error) Log()

Log logs the error using the configured logger.

func (*Error) Stack

func (e *Error) Stack() string

Stack returns the stack trace as a string.

func (*Error) ToJSON

func (e *Error) ToJSON(opts ...FormatOption) (string, error)

ToJSON converts the error to a JSON string.

func (*Error) ToYAML

func (e *Error) ToYAML(opts ...FormatOption) (string, error)

ToYAML converts the error to a YAML string.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap provides compatibility with Go 1.13 error chains.

func (*Error) WithMetadata

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

WithMetadata adds metadata to the error.

Example
err := New("database connection failed").
	WithMetadata("retry_count", 3).
	WithMetadata("last_error", "connection timeout")

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

if retryCount, ok := err.GetMetadata("retry_count"); ok {
	
Output:

Error: database connection failed
Retry count: 3

type ErrorContext

type ErrorContext struct {
	// Timestamp when the error occurred.
	Timestamp time.Time
	// Type categorizes the error.
	Type ErrorType
	// Severity indicates the error's impact level.
	Severity Severity
	// Operation that was being performed.
	Operation string
	// Component where the error originated.
	Component string
	// RequestID for tracing.
	RequestID string
	// User associated with the operation.
	User string
	// Environment where the error occurred.
	Environment string
	// Version of the application.
	Version string
	// File and line where the error occurred.
	File string
	Line int
	// Additional context-specific data.
	Data map[string]any
}

ErrorContext holds comprehensive information about an error's context.

func (*ErrorContext) String

func (ec *ErrorContext) String() string

String returns a formatted string representation of the error context.

type ErrorGroup

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

ErrorGroup represents a collection of related errors. It maintains a reference to its pool for proper release handling.

func NewErrorGroup

func NewErrorGroup() *ErrorGroup

NewErrorGroup creates a standalone ErrorGroup without pooling. This is useful for cases where pooling isn't needed or for testing.

func (*ErrorGroup) Add

func (eg *ErrorGroup) Add(err error)

Add appends an error to the group if it's not nil.

func (*ErrorGroup) Clear

func (eg *ErrorGroup) Clear()

Clear removes all errors from the group while preserving capacity.

func (*ErrorGroup) Error

func (eg *ErrorGroup) Error() string

Error implements the error interface.

func (*ErrorGroup) ErrorOrNil

func (eg *ErrorGroup) ErrorOrNil() error

ErrorOrNil returns the ErrorGroup itself if it contains errors, or nil if empty.

func (*ErrorGroup) Errors

func (eg *ErrorGroup) Errors() []error

Errors returns a copy of all errors in the group.

func (*ErrorGroup) HasErrors

func (eg *ErrorGroup) HasErrors() bool

HasErrors returns true if the group contains any errors.

func (*ErrorGroup) Release

func (eg *ErrorGroup) Release()

Release returns the ErrorGroup to its pool if it came from one. If the ErrorGroup wasn't created from a pool, Release is a no-op.

type ErrorGroupPool

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

ErrorGroupPool manages a pool of ErrorGroup instances. By making this a separate type, we give users control over pool lifecycle and configuration while maintaining encapsulation.

func NewErrorGroupPool

func NewErrorGroupPool(initialCapacity int) *ErrorGroupPool

NewErrorGroupPool creates a new pool for error groups with the specified initial capacity for the error slices.

func (*ErrorGroupPool) Get

func (p *ErrorGroupPool) Get() *ErrorGroup

Get retrieves an ErrorGroup from the pool or creates a new one if the pool is empty.

type ErrorOutput

type ErrorOutput struct {
	// Message contains the main error message
	Message string `json:"message" yaml:"message"`
	// Timestamp indicates when the error occurred
	Timestamp time.Time `json:"timestamp" yaml:"timestamp"`
	// Type categorizes the error
	Type string `json:"type" yaml:"type"`
	// Severity indicates the error's impact level
	Severity string `json:"severity" yaml:"severity"`
	// Stack contains the error stack trace
	Stack string `json:"stack" yaml:"stack"`
	// Cause contains the underlying error if any
	Cause *ErrorOutput `json:"cause,omitempty" yaml:"cause,omitempty"`
	// Context contains additional error context
	Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
	// Metadata contains user-defined metadata
	Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

ErrorOutput represents a formatted error output structure that can be serialized to various formats like JSON and YAML.

type ErrorType

type ErrorType int

ErrorType represents the type of error that occurred.

const (
	// ErrorTypeUnknown represents an unknown error type.
	ErrorTypeUnknown ErrorType = iota
	// ErrorTypeValidation represents a validation error.
	ErrorTypeValidation
	// ErrorTypeNotFound represents a not found error.
	ErrorTypeNotFound
	// ErrorTypePermission represents a permission error.
	ErrorTypePermission
	// ErrorTypeDatabase represents a database error.
	ErrorTypeDatabase
	// ErrorTypeNetwork represents a network error.
	ErrorTypeNetwork
	// ErrorTypeConfiguration represents a configuration error.
	ErrorTypeConfiguration
	// ErrorTypeInternal indicates internal system errors.
	ErrorTypeInternal
	// ErrorTypeExternal indicates errors from external services.
	ErrorTypeExternal
)

func (ErrorType) String

func (et ErrorType) String() string

String returns the string representation of the error type, useful for logging and error reporting.

type FormatOption

type FormatOption func(*ErrorOutput)

FormatOption defines formatting options for error output.

func WithStackTrace

func WithStackTrace(include bool) FormatOption

WithStackTrace controls whether to include the stack trace in the output.

func WithTimestampFormat

func WithTimestampFormat(format string) FormatOption

WithTimestampFormat allows customizing the timestamp format in the output.

type Option

type Option func(*Error)

Option defines the signature for configuration options.

func WithContext

func WithContext(ctx context.Context, errorType ErrorType, severity Severity) Option

WithContext adds context information to the error.

func WithLogger

func WithLogger(logger logger.Logger) Option

WithLogger sets a logger for the error.

func WithRetry

func WithRetry(maxAttempts int, delay time.Duration) Option

WithRetry adds retry information to the error.

type RecoverySuggestion

type RecoverySuggestion struct {
	// Message provides a human-readable explanation.
	Message string
	// Actions lists specific steps that can be taken.
	Actions []string
	// Documentation links to relevant documentation.
	Documentation string
}

RecoverySuggestion provides guidance on how to recover from an error.

type RetryInfo

type RetryInfo struct {
	// MaxAttempts is the maximum number of retry attempts.
	MaxAttempts int
	// CurrentAttempt is the current retry attempt.
	CurrentAttempt int
	// Delay is the delay between retry attempts.
	Delay time.Duration
	// LastAttempt is the timestamp of the last retry attempt.
	LastAttempt time.Time
	// ShouldRetry is a function that determines if a retry should be attempted.
	ShouldRetry func(error) bool
}

RetryInfo holds information about retry attempts.

type Severity

type Severity int

Severity represents the impact level of an error.

const (
	// SeverityInfo indicates an informational message.
	SeverityInfo Severity = iota
	// SeverityWarning indicates a warning that needs attention.
	SeverityWarning
	// SeverityError indicates a significant error.
	SeverityError
	// SeverityCritical indicates a critical system failure.
	SeverityCritical
)

func (Severity) String

func (s Severity) String() string

String returns the string representation of the severity level.

Directories

Path Synopsis
internal
logger
Package logger provides a standardized logging interface for the application.
Package logger provides a standardized logging interface for the application.
pkg
ewrap/adapters
Package adapters provides logging adapters for popular logging frameworks
Package adapters provides logging adapters for popular logging frameworks

Jump to

Keyboard shortcuts

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