errors

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: May 6, 2025 License: MIT Imports: 15 Imported by: 9

README

Enhanced Error Handling for Go with Context, Stack Traces, Monitoring, and More

Go Reference Go Report Card License Benchmarks

A production-grade error handling library for Go, offering zero-cost abstractions, stack traces, multi-error support, retries, and advanced monitoring through two complementary packages: errors (core) and errmgr (management).

Features

errors Package (Core)
  • Performance Optimized

    • Optional memory pooling (12 ns/op with pooling)
    • Lazy stack trace collection (205 ns/op with stack)
    • Small context optimization (≤4 items, 40 ns/op)
    • Lock-free configuration reads
  • Debugging Tools

    • Full stack traces with internal frame filtering
    • Error wrapping and chaining
    • Structured context attachment
    • JSON serialization (662 ns/op)
  • Advanced Utilities

    • Configurable retry mechanism
    • Multi-error aggregation with sampling
    • HTTP status code support
    • Callback triggers for cleanup or side effects
errmgr Package (Management)
  • Production Monitoring
    • Error occurrence counting
    • Threshold-based alerting
    • Categorized metrics
    • Predefined error templates

Installation

go get github.com/olekukonko/errors@latest

Package Overview

  • errors: Core error handling with creation, wrapping, context, stack traces, retries, and multi-error support.
  • errmgr: Error management with templates, monitoring, and predefined errors for consistent application use.

[!NOTE]
✓ added support for errors.Errorf("user %w not found", errors.New("bob"))
✓ added support for sequential chain execution ``

Using the errors Package

Basic Error Creation
Simple Error
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  // Fast error with no stack trace
  err := errors.New("connection failed")
  fmt.Println(err) // "connection failed"

  // Standard error, no allocation, same speed
  stdErr := errors.Std("connection failed")
  fmt.Println(stdErr) // "connection failed"
}
Formatted Error
// main.go
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  // Formatted error without stack trace
  errNoWrap := errors.Newf("user %s not found", "bob")
  fmt.Println(errNoWrap) // Output: "user bob not found"

  // Standard formatted error, no fmt.Errorf needed (using own pkg)
  stdErrNoWrap := errors.Stdf("user %s not found", "bob")
  fmt.Println(stdErrNoWrap) // Output: "user bob not found"

  // Added support for %w (compatible with fmt.Errorf output)
  // errors.Errorf is alias of errors.Newf
  errWrap := errors.Errorf("user %w not found", errors.New("bob"))
  fmt.Println(errWrap) // Output: "user bob not found"

  // Standard formatted error for comparison
  stdErrWrap := fmt.Errorf("user %w not found", fmt.Errorf("bob"))
  fmt.Println(stdErrWrap) // Output: "user bob not found"
}
Error with Stack Trace
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  // Create an error with stack trace using Trace
  err := errors.Trace("critical issue")
  fmt.Println(err)         // Output: "critical issue"
  fmt.Println(err.Stack()) // Output: e.g., ["main.go:15", "caller.go:42"]

  // Convert basic error to traceable with WithStack
  errS := errors.New("critical issue")
  errS = errS.WithStack()   // Add stack trace and update error
  fmt.Println(errS)         // Output: "critical issue"
  fmt.Println(errS.Stack()) // Output: e.g., ["main.go:19", "caller.go:42"]
}
Named Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Create a named error with stack trace
	err := errors.Named("InputError")
	fmt.Println(err.Name()) // Output: "InputError"
	fmt.Println(err)        // Output: "InputError"
}
Adding Context
Basic Context
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Create an error with context
	err := errors.New("processing failed").
		With("id", "123").
		With("attempt", 3).
		With("retryable", true)
	fmt.Println("Error:", err)                    // Output: "processing failed"
	fmt.Println("Full context:", errors.Context(err)) // Output: map[id:123 attempt:3 retryable:true]
}
Context with Wrapped Standard Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Wrap a standard error and add context
	err := errors.New("processing failed").
		With("id", "123")
	wrapped := fmt.Errorf("wrapped: %w", err)
	fmt.Println("Wrapped error:", wrapped)             // Output: "wrapped: processing failed"
	fmt.Println("Direct context:", errors.Context(wrapped)) // Output: nil

	// Convert to access context
	e := errors.Convert(wrapped)
	fmt.Println("Converted context:", e.Context()) // Output: map[id:123]
}
Adding Context to Standard Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Convert a standard error and add context
	stdErr := fmt.Errorf("standard error")
	converted := errors.Convert(stdErr).
		With("source", "legacy").
		With("severity", "high")
	fmt.Println("Message:", converted.Error()) // Output: "standard error"
	fmt.Println("Context:", converted.Context()) // Output: map[source:legacy severity:high]
}
Complex Context
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Create an error with complex context
	err := errors.New("database operation failed").
		With("query", "SELECT * FROM users").
		With("params", map[string]interface{}{
			"limit":  100,
			"offset": 0,
		}).
		With("duration_ms", 45.2)
	fmt.Println("Complex error context:")
	for k, v := range errors.Context(err) {
		fmt.Printf("%s: %v (%T)\n", k, v, v)
	}
	// Output:
	// query: SELECT * FROM users (string)
	// params: map[limit:100 offset:0] (map[string]interface {})
	// duration_ms: 45.2 (float64)
}
Stack Traces
Adding Stack to Any Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Add stack trace to a standard error
	err := fmt.Errorf("basic error")
	enhanced := errors.WithStack(err)
	fmt.Println("Error with stack:")
	fmt.Println("Message:", enhanced.Error()) // Output: "basic error"
	fmt.Println("Stack:", enhanced.Stack())   // Output: e.g., "main.go:15"
}
Chaining with Stack
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
	"time"
)

func main() {
	// Create an enhanced error and add stack/context
	err := errors.New("validation error").
		With("field", "email")
	stackErr := errors.WithStack(err).
		With("timestamp", time.Now()).
		WithCode(500)
	fmt.Println("Message:", stackErr.Error()) // Output: "validation error"
	fmt.Println("Context:", stackErr.Context()) // Output: map[field:email timestamp:...]
	fmt.Println("Stack:")
	for _, frame := range stackErr.Stack() {
		fmt.Println(frame)
	}
}
Stack Traces with WithStack()
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
	"math/rand"
	"time"
)

func basicFunc() error {
	return fmt.Errorf("basic error")
}

func enhancedFunc() *errors.Error {
	return errors.New("enhanced error")
}

func main() {
	// 1. Package-level WithStack - works with ANY error type
	err1 := basicFunc()
	enhanced1 := errors.WithStack(err1) // Handles basic errors
	fmt.Println("Package-level WithStack:")
	fmt.Println(enhanced1.Stack())

	// 2. Method-style WithStack - only for *errors.Error
	err2 := enhancedFunc()
	enhanced2 := err2.WithStack() // More natural chaining
	fmt.Println("\nMethod-style WithStack:")
	fmt.Println(enhanced2.Stack())

	// 3. Combined usage in real-world scenario
	result := processData()
	if result != nil {
		// Use package-level when type is unknown
		stackErr := errors.WithStack(result)

		// Then use method-style for chaining
		finalErr := stackErr.
			With("timestamp", time.Now()).
			WithCode(500)

		fmt.Println("\nCombined Usage:")
		fmt.Println("Message:", finalErr.Error())
		fmt.Println("Context:", finalErr.Context())
		fmt.Println("Stack:")
		for _, frame := range finalErr.Stack() {
			fmt.Println(frame)
		}
	}
}

func processData() error {
	// Could return either basic or enhanced error
	if rand.Intn(2) == 0 {
		return fmt.Errorf("database error")
	}
	return errors.New("validation error").With("field", "email")
}
Error Wrapping and Chaining
Basic Wrapping
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Wrap an error with additional context
	lowErr := errors.New("low-level failure")
	highErr := errors.Wrapf(lowErr, "high-level operation failed: %s", "details")
	fmt.Println(highErr)           // Output: "high-level operation failed: details: low-level failure"
	fmt.Println(errors.Unwrap(highErr)) // Output: "low-level failure"
}
Walking Error Chain
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Create a chained error
	dbErr := errors.New("connection timeout").
		With("server", "db01.prod")
	bizErr := errors.New("failed to process user 12345").
		With("user_id", "12345").
		Wrap(dbErr)
	apiErr := errors.New("API request failed").
		WithCode(500).
		Wrap(bizErr)

	// Walk the error chain
	fmt.Println("Error Chain:")
	for i, e := range errors.UnwrapAll(apiErr) {
		fmt.Printf("%d. %s\n", i+1, e)
	}
	// Output:
	// 1. API request failed
	// 2. failed to process user 12345
	// 3. connection timeout
}
Type Assertions
Using Is
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Check error type with Is
	err := errors.Named("AuthError")
	wrapped := errors.Wrapf(err, "login failed")
	if errors.Is(wrapped, err) {
		fmt.Println("Is an AuthError") // Output: "Is an AuthError"
	}
}
Using As
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Extract error type with As
	err := errors.Named("AuthError")
	wrapped := errors.Wrapf(err, "login failed")
	var authErr *errors.Error
	if wrapped.As(&authErr) {
		fmt.Println("Extracted:", authErr.Name()) // Output: "Extracted: AuthError"
	}
}
Retry Mechanism
Basic Retry
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
  "math/rand"
  "time"
)

func main() {
  // Simulate a flaky operation
  attempts := 0
  retry := errors.NewRetry(
    errors.WithMaxAttempts(3),
    errors.WithDelay(100*time.Millisecond),
  )
  err := retry.Execute(func() error {
    attempts++
    if rand.Intn(2) == 0 {
      return errors.New("temporary failure").WithRetryable()
    }
    return nil
  })
  if err != nil {
    fmt.Printf("Failed after %d attempts: %v\n", attempts, err)
  } else {
    fmt.Printf("Succeeded after %d attempts\n", attempts)
  }
}

Retry with Context Timeout
package main

import (
	"context"
	"fmt"
	"github.com/olekukonko/errors"
	"time"
)

func main() {
	// Retry with context timeout
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	retry := errors.NewRetry(
		errors.WithContext(ctx),
		errors.WithMaxAttempts(5),
		errors.WithDelay(200*time.Millisecond),
	)
	err := retry.Execute(func() error {
		return errors.New("operation failed").WithRetryable()
	})
	if errors.Is(err, context.DeadlineExceeded) {
		fmt.Println("Operation timed out:", err)
	} else if err != nil {
		fmt.Println("Operation failed:", err)
	}
}
Retry Comprehensive
package main

import (
  "context"
  "fmt"
  "github.com/olekukonko/errors"
  "math/rand"
  "time"
)

// DatabaseClient simulates a flaky database connection
type DatabaseClient struct {
  healthyAfterAttempt int
}

func (db *DatabaseClient) Query() error {
  if db.healthyAfterAttempt > 0 {
    db.healthyAfterAttempt--
    return errors.New("database connection failed").
      With("attempt_remaining", db.healthyAfterAttempt).
      WithRetryable() // Mark as retryable
  }
  return nil
}

// ExternalService simulates an unreliable external API
func ExternalService() error {
  if rand.Intn(100) < 30 { // 30% failure rate
    return errors.New("service unavailable").
      WithCode(503).
      WithRetryable()
  }
  return nil
}

func main() {
  // Configure retry with exponential backoff and jitter
  retry := errors.NewRetry(
    errors.WithMaxAttempts(5),
    errors.WithDelay(200*time.Millisecond),
    errors.WithMaxDelay(2*time.Second),
    errors.WithJitter(true),
    errors.WithBackoff(errors.ExponentialBackoff{}),
    errors.WithOnRetry(func(attempt int, err error) {
      // Calculate delay using the same logic as in Execute
      baseDelay := 200 * time.Millisecond
      maxDelay := 2 * time.Second
      delay := errors.ExponentialBackoff{}.Backoff(attempt, baseDelay)
      if delay > maxDelay {
        delay = maxDelay
      }
      fmt.Printf("Attempt %d failed: %v (retrying in %v)\n",
        attempt,
        err.Error(),
        delay)
    }),
  )

  // Scenario 1: Database connection with known recovery point
  db := &DatabaseClient{healthyAfterAttempt: 3}
  fmt.Println("Starting database operation...")
  err := retry.Execute(func() error {
    return db.Query()
  })
  if err != nil {
    fmt.Printf("Database operation failed after %d attempts: %v\n", retry.Attempts(), err)
  } else {
    fmt.Println("Database operation succeeded!")
  }

  // Scenario 2: External service with random failures
  fmt.Println("\nStarting external service call...")
  var lastAttempts int
  start := time.Now()

  // Using ExecuteReply to demonstrate return values
  result, err := errors.ExecuteReply[string](retry, func() (string, error) {
    lastAttempts++
    if err := ExternalService(); err != nil {
      return "", err
    }
    return "service response data", nil
  })

  duration := time.Since(start)

  if err != nil {
    fmt.Printf("Service call failed after %d attempts (%.2f sec): %v\n",
      lastAttempts,
      duration.Seconds(),
      err)
  } else {
    fmt.Printf("Service call succeeded after %d attempts (%.2f sec): %s\n",
      lastAttempts,
      duration.Seconds(),
      result)
  }

  // Scenario 3: Context cancellation with more visibility
  fmt.Println("\nStarting operation with timeout...")
  ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
  defer cancel()

  timeoutRetry := retry.Transform(
    errors.WithContext(ctx),
    errors.WithMaxAttempts(10),
    errors.WithOnRetry(func(attempt int, err error) {
      fmt.Printf("Timeout scenario attempt %d: %v\n", attempt, err)
    }),
  )

  startTimeout := time.Now()
  err = timeoutRetry.Execute(func() error {
    time.Sleep(300 * time.Millisecond) // Simulate long operation
    return errors.New("operation timed out")
  })

  if errors.Is(err, context.DeadlineExceeded) {
    fmt.Printf("Operation cancelled by timeout after %.2f sec: %v\n",
      time.Since(startTimeout).Seconds(),
      err)
  } else if err != nil {
    fmt.Printf("Operation failed: %v\n", err)
  } else {
    fmt.Println("Operation succeeded (unexpected)")
  }
}
Multi-Error Aggregation
Form Validation
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Validate a form with multiple errors
	multi := errors.NewMultiError()
	multi.Add(errors.New("name is required"))
	multi.Add(errors.New("email is invalid"))
	multi.Add(errors.New("password too short"))
	if multi.Has() {
		fmt.Println(multi) // Output: "errors(3): name is required; email is invalid; password too short"
		fmt.Printf("Total errors: %d\n", multi.Count())
	}
}
Sampling Multi-Errors
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  // Simulate many errors with sampling
  multi := errors.NewMultiError(
    errors.WithSampling(10), // 10% sampling
    errors.WithLimit(5),
  )
  for i := 0; i < 100; i++ {
    multi.Add(errors.Newf("error %d", i))
  }
  fmt.Println(multi)
  fmt.Printf("Captured %d out of 100 errors\n", multi.Count())
}
Additional Examples
Using Callbacks
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Add a callback to an error
	err := errors.New("transaction failed").
		Callback(func() {
			fmt.Println("Reversing transaction...")
		})
	fmt.Println(err) // Output: "transaction failed" + "Reversing transaction..."
	err.Free()
}
Copying Errors
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Copy an error and modify the copy
	original := errors.New("base error").With("key", "value")
	copied := original.Copy().With("extra", "data")
	fmt.Println("Original:", original, original.Context()) // Output: "base error" map[key:value]
	fmt.Println("Copied:", copied, copied.Context())       // Output: "base error" map[key:value extra:data]
}
Transforming Errors
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Transform an error with additional context
	err := errors.New("base error")
	transformed := errors.Transform(err, func(e *errors.Error) {
		e.With("env", "prod").
			WithCode(500).
			WithStack()
	})
	fmt.Println(transformed.Error())          // Output: "base error"
	fmt.Println(transformed.Context())        // Output: map[env:prod]
	fmt.Println(transformed.Code())           // Output: 500
	fmt.Println(len(transformed.Stack()) > 0) // Output: true
	transformed.Free()
}
Transformation and Enrichment
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func process() error {
  return errors.New("base error")
}

func main() {
  err := process()
  transformedErr := errors.Transform(err, func(e *errors.Error) {
    e.With("env", "prod").
      WithCode(500).
      WithStack()
  })

  // No type assertion needed now
  fmt.Println(transformedErr.Error())          // "base error"
  fmt.Println(transformedErr.Context())        // map[env:prod]
  fmt.Println(transformedErr.Code())           // 500
  fmt.Println(len(transformedErr.Stack()) > 0) // true
  transformedErr.Free()                        // Clean up

  stdErr := process()
  convertedErr := errors.Convert(stdErr) // Convert standard error to *Error
  convertedErr.With("source", "external").
    WithCode(400).
          Callback(func() {
            fmt.Println("Converted error processed...")
          })
  fmt.Println("Converted Error:", convertedErr.Error())
  fmt.Println("Context:", convertedErr.Context())
  fmt.Println("Code:", convertedErr.Code())
  convertedErr.Free()

}

Fast Stack Trace
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Get a lightweight stack trace
	err := errors.Trace("lightweight error")
	fastStack := err.FastStack()
	fmt.Println("Fast Stack:")
	for _, frame := range fastStack {
		fmt.Println(frame) // Output: e.g., "main.go:15"
	}
}
WarmStackPool
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Pre-warm the stack pool
	errors.WarmStackPool(10)
	err := errors.Trace("pre-warmed error")
	fmt.Println("Stack after warming pool:")
	for _, frame := range err.Stack() {
		fmt.Println(frame)
	}
}
Multi-Error Aggregation
package main

import (
  "fmt"
  "net/mail"
  "strings"
  "time"

  "github.com/olekukonko/errors"
)

type UserForm struct {
  Name     string
  Email    string
  Password string
  Birthday string
}

func validateUser(form UserForm) *errors.MultiError {
  multi := errors.NewMultiError(
    errors.WithLimit(10),
    errors.WithFormatter(customFormat),
  )

  // Name validation
  if form.Name == "" {
    multi.Add(errors.New("name is required"))
  } else if len(form.Name) > 50 {
    multi.Add(errors.New("name cannot exceed 50 characters"))
  }

  // Email validation
  if form.Email == "" {
    multi.Add(errors.New("email is required"))
  } else {
    if _, err := mail.ParseAddress(form.Email); err != nil {
      multi.Add(errors.New("invalid email format"))
    }
    if !strings.Contains(form.Email, "@") {
      multi.Add(errors.New("email must contain @ symbol"))
    }
  }

  // Password validation
  if len(form.Password) < 8 {
    multi.Add(errors.New("password must be at least 8 characters"))
  }
  if !strings.ContainsAny(form.Password, "0123456789") {
    multi.Add(errors.New("password must contain at least one number"))
  }
  if !strings.ContainsAny(form.Password, "!@#$%^&*") {
    multi.Add(errors.New("password must contain at least one special character"))
  }

  // Birthday validation
  if form.Birthday != "" {
    if _, err := time.Parse("2006-01-02", form.Birthday); err != nil {
      multi.Add(errors.New("birthday must be in YYYY-MM-DD format"))
    } else if bday, _ := time.Parse("2006-01-02", form.Birthday); time.Since(bday).Hours()/24/365 < 13 {
      multi.Add(errors.New("must be at least 13 years old"))
    }
  }

  return multi
}

func customFormat(errs []error) string {
  var sb strings.Builder
  sb.WriteString("🚨 Validation Errors:\n")
  for i, err := range errs {
    sb.WriteString(fmt.Sprintf("  %d. %s\n", i+1, err))
  }
  sb.WriteString(fmt.Sprintf("\nTotal issues found: %d\n", len(errs)))
  return sb.String()
}

func main() {
  fmt.Println("=== User Registration Validation ===")

  user := UserForm{
    Name:     "", // Empty name
    Email:    "invalid-email",
    Password: "weak",
    Birthday: "2015-01-01", // Under 13
  }

  // Generate multiple validation errors
  validationErrors := validateUser(user)

  if validationErrors.Has() {
    fmt.Println(validationErrors)

    // Detailed error analysis
    fmt.Println("\n🔍 Error Analysis:")
    fmt.Printf("Total errors: %d\n", validationErrors.Count())
    fmt.Printf("First error: %v\n", validationErrors.First())
    fmt.Printf("Last error: %v\n", validationErrors.Last())

    // Categorized errors with consistent formatting
    fmt.Println("\n📋 Error Categories:")
    if emailErrors := validationErrors.Filter(contains("email")); emailErrors.Has() {
      fmt.Println("Email Issues:")
      if emailErrors.Count() == 1 {
        fmt.Println(customFormat([]error{emailErrors.First()}))
      } else {
        fmt.Println(emailErrors)
      }
    }
    if pwErrors := validationErrors.Filter(contains("password")); pwErrors.Has() {
      fmt.Println("Password Issues:")
      if pwErrors.Count() == 1 {
        fmt.Println(customFormat([]error{pwErrors.First()}))
      } else {
        fmt.Println(pwErrors)
      }
    }
    if ageErrors := validationErrors.Filter(contains("13 years")); ageErrors.Has() {
      fmt.Println("Age Restriction:")
      if ageErrors.Count() == 1 {
        fmt.Println(customFormat([]error{ageErrors.First()}))
      } else {
        fmt.Println(ageErrors)
      }
    }
  }

  // System Error Aggregation Example
  fmt.Println("\n=== System Error Aggregation ===")
  systemErrors := errors.NewMultiError(
    errors.WithLimit(5),
    errors.WithFormatter(systemErrorFormat),
  )

  // Simulate system errors
  systemErrors.Add(errors.New("database connection timeout").WithRetryable())
  systemErrors.Add(errors.New("API rate limit exceeded").WithRetryable())
  systemErrors.Add(errors.New("disk space low"))
  systemErrors.Add(errors.New("database connection timeout").WithRetryable()) // Duplicate
  systemErrors.Add(errors.New("cache miss"))
  systemErrors.Add(errors.New("database connection timeout").WithRetryable()) // Over limit

  fmt.Println(systemErrors)
  fmt.Printf("\nSystem Status: %d active issues\n", systemErrors.Count())

  // Filter retryable errors
  if retryable := systemErrors.Filter(errors.IsRetryable); retryable.Has() {
    fmt.Println("\n🔄 Retryable Errors:")
    fmt.Println(retryable)
  }
}

func systemErrorFormat(errs []error) string {
  var sb strings.Builder
  sb.WriteString("⚠️ System Alerts:\n")
  for i, err := range errs {
    sb.WriteString(fmt.Sprintf("  %d. %s", i+1, err))
    if errors.IsRetryable(err) {
      sb.WriteString(" (retryable)")
    }
    sb.WriteString("\n")
  }
  return sb.String()
}

func contains(substr string) func(error) bool {
  return func(err error) bool {
    return strings.Contains(err.Error(), substr)
  }
}
Chain Execution
Sequential Task Processing
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
  "time"
)

// validateOrder checks order input.
func validateOrder() error {
  return nil // Simulate successful validation
}

// processKYC handles payment processing.
func processKYC() error {
  return nil // Simulate successful validation
}

// processPayment handles payment processing.
func processPayment() error {
  return errors.New("payment declined") // Simulate payment failure
}

// generateInvoice creates an invoice.
func generateInvoice() error {
  return errors.New("invoicing unavailable") // Simulate invoicing issue
}

// sendNotification sends a confirmation.
func sendNotification() error {
  return errors.New("notification failed") // Simulate notification failure
}

// processOrder simulates a multi-step order processing workflow.
func processOrder() error {
  c := errors.NewChain()

  // Validate order input
  c.Step(validateOrder).Tag("validation")

  // KYC  Process
  c.Step(validateOrder).Tag("validation")

  // Process payment with retries
  c.Step(processPayment).Tag("billing").Retry(3, 100*time.Millisecond)

  // Generate invoice
  c.Step(generateInvoice).Tag("invoicing")

  // Send notification (optional)
  c.Step(sendNotification).Tag("notification").Optional()

  return c.Run()
}

func main() {
  if err := processOrder(); err != nil {
    // Print error to stderr and exit
    errors.Inspect(err)
  }
  fmt.Println("Order processed successfully")
}
Sequential Task Processing 2
package main

import (
	"fmt"
	"os"

	"github.com/olekukonko/errors"
)

// validate simulates a validation check that fails.
func validate(name string) error {
	return errors.Newf("validation for %s failed", name)
}

// validateOrder checks order input.
func validateOrder() error {
	return nil // Simulate successful validation
}

// verifyKYC handles Know Your Customer verification.
func verifyKYC(name string) error {
	return validate(name) // Simulate KYC validation failure
}

// processPayment handles payment processing.
func processPayment() error {
	return nil // Simulate successful payment
}

// processOrder coordinates the order processing workflow.
func processOrder() error {
	chain := errors.NewChain().
		Step(validateOrder).     // Step 1: Validate order
		Call(verifyKYC, "john"). // Step 2: Verify customer
		Step(processPayment)     // Step 3: Process payment

	if err := chain.Run(); err != nil {
		return errors.Errorf("processing order: %w", err)
	}
	return nil
}

func main() {
	if err := processOrder(); err != nil {
		// Print the full error chain to stderr
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
		// Output
		// ERROR: processing order: validation for john failed

		// For debugging, you could print the stack trace:
		// errors.Inspect(err)
		os.Exit(1)
	}

	fmt.Println("order processed successfully")
}

Retry with Timeout
package main

import (
  "context"
  "fmt"
  "github.com/olekukonko/errors"
  "time"
)

func main() {
  c := errors.NewChain(
    errors.ChainWithTimeout(1*time.Second),
  ).
          Step(func() error {
            time.Sleep(2 * time.Second)
            return errors.New("fetch failed")
          }).
    Tag("api").
    Retry(3, 200*time.Millisecond)

  err := c.Run()
  if err != nil {
    var deadlineErr error
    if errors.As(err, &deadlineErr) && deadlineErr == context.DeadlineExceeded {
      fmt.Println("Fetch timed out")
    } else {
      fmt.Printf("Fetch failed: %v\n", err)
    }
    return
  }
  fmt.Println("Fetch succeeded")
}
Collecting All Errors
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  c := errors.NewChain(
    errors.ChainWithMaxErrors(2),
  ).
    Step(func() error { return errors.New("task 1 failed") }).Tag("task1").
    Step(func() error { return nil }).Tag("task2").
    Step(func() error { return errors.New("task 3 failed") }).Tag("task3")

  err := c.RunAll()
  if err != nil {
    errors.Inspect(err)
    return
  }
  fmt.Println("All tasks completed successfully")
}


Using the errmgr Package

Predefined Errors
Static Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors/errmgr"
)

func main() {
	// Use a predefined static error
	err := errmgr.ErrNotFound
	fmt.Println(err)        // Output: "not found"
	fmt.Println(err.Code()) // Output: 404
}
Templated Error
package main

import (
	"fmt"
	"github.com/olekukonko/errors/errmgr"
)

func main() {
	// Use a templated error with category
	err := errmgr.ErrDBQuery("SELECT failed")
	fmt.Println(err)           // Output: "database query failed: SELECT failed"
	fmt.Println(err.Category()) // Output: "database"
}
Error Monitoring
Basic Monitoring
package main

import (
	"fmt"
	"github.com/olekukonko/errors/errmgr"
	"time"
)

func main() {
	// Define and monitor an error
	netErr := errmgr.Define("NetError", "network issue: %s")
	monitor := errmgr.NewMonitor("NetError")
	errmgr.SetThreshold("NetError", 2)
	defer monitor.Close()

	go func() {
		for alert := range monitor.Alerts() {
			fmt.Printf("Alert: %s, count: %d\n", alert.Error(), alert.Count())
		}
	}()

	for i := 0; i < 4; i++ {
		err := netErr(fmt.Sprintf("attempt %d", i))
		err.Free()
	}
	time.Sleep(100 * time.Millisecond)
}
Realistic Monitoring
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
	"github.com/olekukonko/errors/errmgr"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	// Define our error types
	netErr := errmgr.Define("NetError", "network connection failed: %s (attempt %d)")
	dbErr := errmgr.Define("DBError", "database operation failed: %s")

	// Create monitors with different buffer sizes
	netMonitor := errmgr.NewMonitorBuffered("NetError", 10) // Larger buffer for network errors
	dbMonitor := errmgr.NewMonitorBuffered("DBError", 5)    // Smaller buffer for DB errors
	defer netMonitor.Close()
	defer dbMonitor.Close()

	// Set different thresholds
	errmgr.SetThreshold("NetError", 3) // Alert after 3 network errors
	errmgr.SetThreshold("DBError", 2)  // Alert after 2 database errors

	// Set up signal handling for graceful shutdown
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// Alert handler goroutine
	done := make(chan struct{})
	go func() {
		defer close(done)
		for {
			select {
			case alert, ok := <-netMonitor.Alerts():
				if !ok {
					fmt.Println("Network alert channel closed")
					return
				}
				handleAlert("NETWORK", alert)
			case alert, ok := <-dbMonitor.Alerts():
				if !ok {
					fmt.Println("Database alert channel closed")
					return
				}
				handleAlert("DATABASE", alert)
			case <-time.After(2 * time.Second):
				// Periodic check for shutdown
				continue
			}
		}
	}()

	// Simulate operations with potential failures
	go func() {
		for i := 1; i <= 15; i++ {
			// Simulate different error scenarios
			if i%4 == 0 {
				// Database error
				err := dbErr("connection timeout")
				fmt.Printf("DB Operation %d: Failed\n", i)
				err.Free()
			} else {
				// Network error
				var errMsg string
				switch {
				case i%3 == 0:
					errMsg = "timeout"
				case i%5 == 0:
					errMsg = "connection reset"
				default:
					errMsg = "unknown error"
				}

				err := netErr(errMsg, i)
				fmt.Printf("Network Operation %d: Failed with %q\n", i, errMsg)
				err.Free()
			}

			// Random delay between operations
			time.Sleep(time.Duration(100+(i%200)) * time.Millisecond)
		}
	}()

	// Wait for shutdown signal or completion
	select {
	case <-sigChan:
		fmt.Println("\nReceived shutdown signal...")
	case <-time.After(5 * time.Second):
		fmt.Println("Completion timeout reached...")
	}

	// Cleanup
	fmt.Println("Initiating shutdown...")
	netMonitor.Close()
	dbMonitor.Close()

	// Wait for the alert handler to finish
	select {
	case <-done:
		fmt.Println("Alert handler shutdown complete")
	case <-time.After(1 * time.Second):
		fmt.Println("Alert handler shutdown timeout")
	}

	fmt.Println("Application shutdown complete")
}

func handleAlert(service string, alert *errors.Error) {
	if alert == nil {
		fmt.Printf("[%s] Received nil alert\n", service)
		return
	}

	fmt.Printf("[%s ALERT] %s (total occurrences: %d)\n",
		service, alert.Error(), alert.Count())

	if alert.Count() > 5 {
		fmt.Printf("[%s CRITICAL] High error rate detected!\n", service)
	}
}

Performance Optimization

Configuration Tuning
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Tune error package configuration
	errors.Configure(errors.Config{
		StackDepth:     32,    // Limit stack frames
		ContextSize:    4,     // Optimize small contexts
		DisablePooling: false, // Enable pooling
	})
	err := errors.New("configured error")
	fmt.Println(err) // Output: "configured error"
}
Using Free() for Performance
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Use Free() to return error to pool
	err := errors.New("temp error")
	fmt.Println(err) // Output: "temp error"
	err.Free()       // Immediate pool return, reduces GC pressure
}
Benchmarks

Real performance data (Apple M3 Pro, Go 1.21):

goos: darwin
goarch: arm64
pkg: github.com/olekukonko/errors
cpu: Apple M3 Pro
BenchmarkBasic_New-12            99810412    12.00 ns/op    0 B/op    0 allocs/op
BenchmarkStack_WithStack-12      5879510    205.6 ns/op   24 B/op    1 allocs/op
BenchmarkContext_Small-12       29600850    40.34 ns/op   16 B/op    1 allocs/op
BenchmarkWrapping_Simple-12    100000000    11.73 ns/op    0 B/op    0 allocs/op
  • New with Pooling: 12 ns/op, 0 allocations
  • WithStack: 205 ns/op, minimal allocation
  • Context: 40 ns/op for small contexts
  • Run: go test -bench=. -benchmem

Migration Guide

From Standard Library
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Before: Standard library error
	err1 := fmt.Errorf("error: %v", "oops")
	fmt.Println(err1)

	// After: Enhanced error with context and stack
	err2 := errors.Newf("error: %v", "oops").
		With("source", "api").
		WithStack()
	fmt.Println(err2)
}
From pkg/errors
package main

import (
  "fmt"
  "github.com/olekukonko/errors"
)

func main() {
  // Before: pkg/errors (assuming similar API)
  // err := pkgerrors.Wrap(err, "context")

  // After: Enhanced wrapping
  err := errors.New("low-level").
    Msgf("context: %s", "details").
    WithStack()
  fmt.Println(err)
}
Compatibility with errors.Is and errors.As
package main

import (
	"fmt"
	"github.com/olekukonko/errors"
)

func main() {
	// Check compatibility with standard library
	err := errors.Named("MyError")
	wrapped := errors.Wrapf(err, "outer")
	if errors.Is(wrapped, err) { // Stdlib compatible
		fmt.Println("Matches MyError") // Output: "Matches MyError"
	}
}

FAQ

  • When to use Copy()?

    • Use SOCIALCopy() to create a modifiable duplicate of an error without altering the original.
  • When to use Free()?

    • Use in performance-critical loops; otherwise, autofree handles it (Go 1.24+).
  • How to handle cleanup?

    • Use Callback() for automatic actions like rollbacks or logging.
  • How to add stack traces later?

    • Use WithStack() to upgrade a simple error:
      package main
      
      import (
          "fmt"
          "github.com/olekukonko/errors"
      )
      
      func main() {
          err := errors.New("simple")
          err = err.WithStack()
          fmt.Println(err.Stack())
      }
      

Contributing

License

MIT License - See LICENSE.

Documentation

Overview

Package errors provides a robust error handling library with support for error wrapping, stack traces, context storage, and retry mechanisms. It extends the standard library's error interface with features like HTTP-like status codes, error categorization, and JSON serialization, while maintaining compatibility with `errors.Is`, `errors.As`, and `errors.Unwrap`. The package is thread-safe and optimized with object pooling for performance.

pool.go

Package errors provides utilities for error handling, including a flexible retry mechanism.

Package errors provides utility functions for error handling, including stack trace capture and function name extraction.

Index

Constants

View Source
const (
	DefaultCode = 500 // Default HTTP status code for errors if not specified.
)

Constants defining default configuration and context keys.

Variables

This section is empty.

Functions

func As

func As(err error, target interface{}) bool

As wraps errors.As, using custom type assertion for *Error types. Falls back to standard errors.As for non-*Error types. Returns false if either err or target is nil.

func Caller

func Caller(skip int) (file string, line int, function string)

Caller returns the file, line, and function name of the caller at the specified skip level. Skip=0 returns the caller of this function, 1 returns its caller, etc.; returns "unknown" if no caller found.

func Category

func Category(err error) string

Category returns the category of an error, if it is an *Error. Returns an empty string for non-*Error types or unset categories.

func Code

func Code(err error) int

Code returns the status code of an error, if it is an *Error. Returns 500 as a default for non-*Error types to indicate an internal error.

func Configure

func Configure(cfg Config)

Configure updates the global configuration for the errors package. It is thread-safe and should be called early to avoid race conditions. Changes apply to all subsequent error operations. Example:

errors.Configure(errors.Config{StackDepth: 16, DisablePooling: true})

func Context

func Context(err error) map[string]interface{}

Context extracts the context map from an error, if it is an *Error. Returns nil for non-*Error types or if no context is present.

func Count

func Count(err error) uint64

Count returns the occurrence count of an error, if it is an *Error. Returns 0 for non-*Error types.

func ExecuteReply

func ExecuteReply[T any](r *Retry, fn func() (T, error)) (T, error)

ExecuteReply runs the provided function with retry logic and returns its result. Returns the result and nil on success, or zero value and last error on failure; generic type T.

func Find

func Find(err error, pred func(error) bool) error

Find searches the error chain for the first error matching pred. Returns nil if no match is found or pred is nil; traverses both Unwrap() and Cause() chains.

func FmtErrorCheck

func FmtErrorCheck(format string, args ...interface{}) (result string, err error)

FmtErrorCheck safely formats a string using fmt.Sprintf, catching panics. Returns the formatted string and any error encountered. Internal use by Newf to validate format strings. Example:

result, err := FmtErrorCheck("value: %s", "test")

func FormatError

func FormatError(err error) string

FormatError returns a formatted string representation of an error. Includes message, name, context, stack trace, and cause for *Error types; just message for others; "<nil>" if nil.

func Has

func Has(err error) bool

Has checks if an error contains meaningful content. Returns true for non-nil standard errors or *Error with content (msg, name, template, or cause).

func HasContextKey

func HasContextKey(err error, key string) bool

HasContextKey checks if the error's context contains the specified key. Returns false for non-*Error types or if the key is not present in the context.

func Inspect

func Inspect(err error)

Inspect provides detailed examination of an error, handling both single errors and MultiError

func InspectError

func InspectError(err *Error)

InspectError provides detailed inspection of a specific *Error instance

func Is

func Is(err, target error) bool

Is wraps errors.Is, using custom matching for *Error types. Falls back to standard errors.Is for non-*Error types; returns true if err equals target.

func IsEmpty

func IsEmpty(err error) bool

IsEmpty checks if an error has no meaningful content. Returns true for nil errors, empty *Error instances, or standard errors with whitespace-only messages.

func IsError

func IsError(err error) bool

IsError checks if an error is an instance of *Error. Returns true only for this package's custom error type; false for nil or other types.

func IsNull

func IsNull(err error) bool

IsNull checks if an error is nil or represents a NULL value. Delegates to *Error’s IsNull for custom errors; uses sqlNull for others.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable checks if an error is retryable. For *Error, checks context for retry flag; for others, looks for "retry" or timeout in message. Returns false for nil errors; thread-safe for *Error types.

func IsTimeout

func IsTimeout(err error) bool

IsTimeout checks if an error indicates a timeout. For *Error, checks context for timeout flag; for others, looks for "timeout" in message. Returns false for nil errors.

func Name

func Name(err error) string

Name returns the name of an error, if it is an *Error. Returns an empty string for non-*Error types or unset names.

func Stack

func Stack(err error) []string

Stack extracts the stack trace from an error, if it is an *Error. Returns nil for non-*Error types or if no stack is present.

func Std

func Std(text string) error

Std creates a standard error using errors.New for compatibility. Does not capture stack traces or add context. Example:

err := errors.Std("simple error")

func Stdf

func Stdf(format string, a ...interface{}) error

Stdf creates a formatted standard error using fmt.Errorf for compatibility. Supports %w for wrapping; does not capture stack traces. Example:

err := errors.Stdf("failed: %w", cause)

func Unwrap

func Unwrap(err error) error

Unwrap returns the underlying cause of an error, if it implements Unwrap. For *Error, returns cause; for others, returns the error itself; nil if err is nil.

func UnwrapAll

func UnwrapAll(err error) []error

UnwrapAll returns a slice of all errors in the chain, including the root error. Traverses both Unwrap() and Cause() chains; returns nil if err is nil.

func Walk

func Walk(err error, fn func(error))

Walk traverses the error chain, applying fn to each error. Supports both Unwrap() and Cause() interfaces; stops at nil or non-unwrappable errors.

func WarmPool

func WarmPool(count int)

WarmPool pre-populates the error pool with count instances. Improves performance by reducing initial allocations. No-op if pooling is disabled. Example:

errors.WarmPool(1000)

func WarmStackPool

func WarmStackPool(count int)

WarmStackPool pre-populates the stack pool with count slices. Improves performance for stack-intensive operations. No-op if pooling is disabled. Example:

errors.WarmStackPool(500)

func With

func With(err error, key string, value interface{}) error

With adds a key-value pair to an error's context, if it is an *Error. Returns the original error unchanged if not an *Error; no-op for non-*Error types.

Types

type BackoffStrategy

type BackoffStrategy interface {
	// Backoff returns the delay for a given attempt based on the base delay.
	Backoff(attempt int, baseDelay time.Duration) time.Duration
}

BackoffStrategy defines the interface for calculating retry delays.

type Chain

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

Chain executes functions sequentially with enhanced error handling. Logging is optional and configured via a slog.Handler.

func NewChain

func NewChain(opts ...ChainOption) *Chain

NewChain creates a new Chain with the given options. Logging is disabled by default (logHandler is nil).

func (*Chain) Call

func (c *Chain) Call(fn interface{}, args ...interface{}) *Chain

Call adds a step by wrapping a function with arguments. It uses reflection to validate and invoke the function.

func (*Chain) Code

func (c *Chain) Code(code int) *Chain

Code sets a numeric error code for the last step.

func (*Chain) Errors

func (c *Chain) Errors() []error

Errors returns a copy of the collected errors.

func (*Chain) HasErrors

func (c *Chain) HasErrors() bool

HasErrors checks if any errors were collected.

func (*Chain) LastError

func (c *Chain) LastError() error

LastError returns the most recent error or nil if none exist.

func (*Chain) Len

func (c *Chain) Len() int

Len returns the number of steps in the chain.

func (*Chain) LogOnFail

func (c *Chain) LogOnFail() *Chain

LogOnFail enables automatic logging of errors for the last step.

func (*Chain) MaxErrors

func (c *Chain) MaxErrors(max int) *Chain

MaxErrors sets the maximum number of errors allowed.

func (*Chain) Optional

func (c *Chain) Optional() *Chain

Optional marks the last step as optional. Optional steps don't stop the chain on error.

func (*Chain) Reset

func (c *Chain) Reset()

Reset clears the chain's steps, errors, and context.

func (*Chain) Retry

func (c *Chain) Retry(maxAttempts int, delay time.Duration, opts ...RetryOption) *Chain

Retry configures retry behavior for the last step. Retry configures retry behavior for the last step.

func (*Chain) Run

func (c *Chain) Run() error

Run executes the chain, stopping on the first non-optional error. It returns the first error encountered or nil if all steps succeed.

func (*Chain) RunAll

func (c *Chain) RunAll() error

RunAll executes all steps, collecting errors without stopping. It returns a MultiError containing all errors or nil if none occurred.

func (*Chain) Step

func (c *Chain) Step(fn func() error) *Chain

Step adds a new step to the chain with the provided function. The function must return an error or nil.

func (*Chain) Tag

func (c *Chain) Tag(category ErrorCategory) *Chain

Tag sets an error category for the last step.

func (*Chain) Timeout

func (c *Chain) Timeout(d time.Duration) *Chain

Timeout sets a timeout for the entire chain.

func (*Chain) Unwrap

func (c *Chain) Unwrap() []error

Unwrap returns the collected errors (alias for Errors).

func (*Chain) With

func (c *Chain) With(key string, value interface{}) *Chain

With adds a key-value pair to the last step's context.

func (*Chain) WithLog

func (c *Chain) WithLog(attrs ...slog.Attr) *Chain

WithLog adds logging attributes to the last step.

type ChainOption

type ChainOption func(*Chain)

ChainOption defines a function that configures a Chain.

func ChainWithAutoWrap

func ChainWithAutoWrap(auto bool) ChainOption

ChainWithAutoWrap enables or disables automatic error wrapping.

func ChainWithLogHandler

func ChainWithLogHandler(handler slog.Handler) ChainOption

ChainWithLogHandler sets a custom slog.Handler for logging. If handler is nil, logging is effectively disabled.

func ChainWithMaxErrors

func ChainWithMaxErrors(max int) ChainOption

ChainWithMaxErrors sets the maximum number of errors allowed. A value <= 0 means no limit.

func ChainWithTimeout

func ChainWithTimeout(d time.Duration) ChainOption

ChainWithTimeout sets a timeout for the entire chain.

type Config

type Config struct {
	StackDepth     int  // Maximum stack trace depth; 0 uses default (32).
	ContextSize    int  // Initial context map size; 0 uses default (4).
	DisablePooling bool // If true, disables object pooling for errors.
	FilterInternal bool // If true, filters internal package frames from stack traces.
	AutoFree       bool // If true, automatically frees errors to pool after use.
}

Config defines the global configuration for the errors package, controlling stack depth, context size, pooling, and frame filtering.

type ConstantBackoff

type ConstantBackoff struct{}

ConstantBackoff provides a fixed delay for each retry attempt.

func (ConstantBackoff) Backoff

func (c ConstantBackoff) Backoff(_ int, baseDelay time.Duration) time.Duration

Backoff returns the base delay regardless of the attempt number. Implements BackoffStrategy with a constant delay.

type Error

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

Error is a custom error type with enhanced features: message, name, stack trace, context, cause, and metadata like code and category. It is thread-safe and supports pooling for performance.

func Convert

func Convert(err error) *Error

Convert transforms any error into an *Error, preserving its message and wrapping it if needed. Returns nil if the input is nil; returns the original if already an *Error. Uses multiple strategies: direct assertion, errors.As, manual unwrapping, and fallback creation.

func Empty

func Empty() *Error

Empty returns a new empty error with no message, name, or stack trace. Useful for incrementally building errors or as a neutral base. Example:

err := errors.Empty().With("key", "value").WithCode(400)

func Errorf

func Errorf(format string, args ...interface{}) *Error

Errorf is an alias for Newf, providing a familiar interface compatible with fmt.Errorf. It creates a formatted error without capturing a stack trace. See Newf for full details on formatting, including %w support for error wrapping.

Example:

err := errors.Errorf("failed: %w", errors.New("cause"))
// err.Error() == "failed: cause"

func From

func From(err error) *Error

From transforms any error into an *Error, preserving its message and wrapping it if needed. Alias of Convert; returns nil if input is nil, original if already an *Error.

func FromContext

func FromContext(ctx context.Context, err error) *Error

FromContext creates an *Error from a context and an existing error. Enhances the error with context info: timeout status, deadline, or cancellation. Returns nil if input error is nil; does not store context values directly.

func Merge

func Merge(errs ...error) *Error

Merge combines multiple errors into a single *Error. Aggregates messages with "; " separator, merges contexts and stacks; returns nil if no errors provided.

func Named

func Named(name string) *Error

Named creates an error with the specified name and captures a stack trace. The name doubles as the error message if no message is set. Use for errors where type identification and stack context are important. Example:

err := errors.Named("AuthError").WithCode(401)

func New

func New(text string) *Error

New creates a lightweight error with the given message and no stack trace. Optimized for performance; use Trace() for stack traces. Returns a shared empty error for empty messages to reduce allocations. Example:

err := errors.New("invalid input")

func Newf

func Newf(format string, args ...interface{}) *Error

Newf creates a formatted error, supporting the %w verb for wrapping errors. If the format contains exactly one %w verb with a non-nil error argument, the error is wrapped as the cause. The final error message string generated by Error() will be compatible with the output of fmt.Errorf for the same inputs. Does not capture a stack trace by default. Example:

cause := errors.New("db error")
err := errors.Newf("query failed: %w", cause)
// err.Error() will match fmt.Errorf("query failed: %w", cause).Error()
// errors.Unwrap(err) == cause

func Trace

func Trace(text string) *Error

Trace creates an error with the given message and captures a stack trace. Use when debugging context is needed; for performance, prefer New(). Example:

err := errors.Trace("operation failed")

func Tracef

func Tracef(format string, args ...interface{}) *Error

Tracef creates a formatted error with a stack trace. Supports %w for wrapping errors. Example:

err := errors.Tracef("query %s failed: %w", query, cause)

func Transform

func Transform(err error, fn func(*Error)) *Error

Transform applies transformations to an error, returning a new *Error. Creates a new *Error from non-*Error types before applying fn; returns nil if err is nil.

func WithStack

func WithStack(err error) *Error

WithStack converts any error to an *Error and captures a stack trace. Returns nil if input is nil; adds stack to existing *Error or wraps non-*Error types.

func Wrap

func Wrap(err error, wrapper *Error) *Error

Wrap creates a new *Error that wraps another error with additional context. Uses a copy of the provided wrapper *Error; returns nil if err is nil.

func Wrapf

func Wrapf(err error, format string, args ...interface{}) *Error

Wrapf creates a new formatted *Error that wraps another error. Formats the message and sets the cause; returns nil if err is nil.

func (*Error) As

func (e *Error) As(target interface{}) bool

As attempts to assign the error or one in its chain to the target interface. Supports *Error and standard error types, traversing the cause chain. Returns true if successful. Example:

var target *Error
if errors.As(err, &target) {
  fmt.Println(target.Name())
}

func (*Error) Callback

func (e *Error) Callback(fn func()) *Error

Callback sets a function to be called when Error() is invoked. Useful for logging or side effects on error access. Example:

err := errors.New("test").Callback(func() { log.Println("error accessed") })

func (*Error) Category

func (e *Error) Category() string

Category returns the error’s category, if set. Example:

if err.Category() == "network" {
  handleNetworkError(err)
}

func (*Error) Code

func (e *Error) Code() int

Code returns the error’s HTTP-like status code, if set. Returns 0 if no code is set. Example:

if err.Code() == 404 {
  renderNotFound()
}

func (*Error) Context

func (e *Error) Context() map[string]interface{}

Context returns the error’s context as a map, merging smallContext and map-based context. Thread-safe; lazily initializes the map if needed. Example:

ctx := err.Context()
if userID, ok := ctx["user_id"]; ok {
  fmt.Println(userID)
}

func (*Error) Copy

func (e *Error) Copy() *Error

Copy creates a deep copy of the error, preserving all fields except stack freshness. The new error can be modified independently. Example:

newErr := err.Copy().With("new_key", "value")

func (*Error) Count

func (e *Error) Count() uint64

Count returns the number of times the error has been incremented. Useful for tracking error frequency. Example:

fmt.Printf("Error occurred %d times", err.Count())

func (*Error) Err

func (e *Error) Err() error

Err returns the error as an error interface. Useful for type assertions or interface compatibility. Example:

var stdErr error = err.Err()

func (*Error) Error

func (e *Error) Error() string

Error returns the string representation of the error. If the error was created using Newf/Errorf with the %w verb, it returns the pre-formatted string compatible with fmt.Errorf. Otherwise, it combines the message, template, or name with the cause's error string, separated by ": ". Invokes any set callback.

func (*Error) FastStack

func (e *Error) FastStack() []string

FastStack returns a lightweight stack trace with file and line numbers only. Omits function names for performance; skips internal frames if configured. Returns nil if no stack trace exists. Example:

for _, frame := range err.FastStack() {
  fmt.Println(frame) // e.g., "main.go:42"
}

func (*Error) Find

func (e *Error) Find(pred func(error) bool) error

Find searches the error chain for the first error where pred returns true. Returns nil if no match is found or if pred is nil. Example:

err := err.Find(func(e error) bool { return strings.Contains(e.Error(), "timeout") })

func (*Error) Format

func (e *Error) Format() string

Format returns a detailed, human-readable string representation of the error, including message, code, context, stack, and cause. Recursive for causes that are also *Error. Example:

fmt.Println(err.Format())
// Output:
// Error: failed: cause
// Code: 500
// Context:
//   key: value
// Stack:
//   1. main.main main.go:42

func (*Error) Free

func (e *Error) Free()

Free resets the error and returns it to the pool if pooling is enabled. Safe to call multiple times; no-op if pooling is disabled. Call after use to prevent memory leaks when autoFree is false. Example:

defer err.Free()

func (*Error) Has

func (e *Error) Has() bool

Has checks if the error contains meaningful content (message, template, name, or cause). Returns false for nil or empty errors. Example:

if !err.Has() {
  return nil
}

func (*Error) HasContextKey

func (e *Error) HasContextKey(key string) bool

HasContextKey checks if the specified key exists in the error’s context. Thread-safe; checks both smallContext and map-based context. Example:

if err.HasContextKey("user_id") {
  fmt.Println(err.Context()["user_id"])
}

func (*Error) Increment

func (e *Error) Increment() *Error

Increment atomically increases the error’s count by 1 and returns the error. Useful for tracking repeated occurrences. Example:

err := err.Increment()

func (*Error) Is

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

Is checks if the error matches the target by pointer, name, or cause chain. Compatible with errors.Is; also matches by string for standard errors. Returns true if the error or its cause matches the target. Example:

if errors.Is(err, errors.New("target")) {
  handleTargetError()
}

func (*Error) IsEmpty

func (e *Error) IsEmpty() bool

IsEmpty checks if the error lacks meaningful content (no message, name, template, or cause). Returns true for nil or fully empty errors. Example:

if err.IsEmpty() {
  return nil
}

func (*Error) IsNull

func (e *Error) IsNull() bool

IsNull checks if the error is nil, empty, or contains only SQL NULL values in its context or cause. Useful for handling database-related errors. Example:

if err.IsNull() {
  return nil
}

func (*Error) MarshalJSON

func (e *Error) MarshalJSON() ([]byte, error)

MarshalJSON serializes the error to JSON, including name, message, context, cause, stack, and code. Causes are recursively serialized if they implement json.Marshaler or are *Error. Example:

data, _ := json.Marshal(err)
fmt.Println(string(data))

func (*Error) Msgf

func (e *Error) Msgf(format string, args ...interface{}) *Error

Msgf sets the error’s message using a formatted string and returns the error. Overwrites any existing message. Example:

err := err.Msgf("user %s not found", username)

func (*Error) Name

func (e *Error) Name() string

Name returns the error’s name, if set. Example:

if err.Name() == "AuthError" {
  handleAuthError()
}

func (*Error) Reset

func (e *Error) Reset()

Reset clears all fields of the error, preparing it for reuse in the pool. Internal use by Free; does not release stack to stackPool. Example:

err.Reset() // Clear all fields.

func (*Error) Stack

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

Stack returns a detailed stack trace with function names, files, and line numbers. Filters internal frames if configured; returns nil if no stack exists. Example:

for _, frame := range err.Stack() {
  fmt.Println(frame) // e.g., "main.main main.go:42"
}

func (*Error) Trace

func (e *Error) Trace() *Error

Trace ensures the error has a stack trace, capturing it if absent. Returns the error for chaining. Example:

err := errors.New("failed").Trace()

func (*Error) Transform

func (e *Error) Transform(fn func(*Error)) *Error

Transform applies transformations to a copy of the error and returns the new error. The original error is unchanged; nil-safe. Example:

newErr := err.Transform(func(e *Error) { e.With("key", "value") })

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause of the error, if any. Compatible with errors.Unwrap for chain traversal. Example:

cause := errors.Unwrap(err)

func (*Error) UnwrapAll

func (e *Error) UnwrapAll() []error

UnwrapAll returns a slice of all errors in the chain, starting with this error. Each error is isolated to prevent modifications affecting others. Example:

chain := err.UnwrapAll()
for _, e := range chain {
  fmt.Println(e.Error())
}

func (*Error) Walk

func (e *Error) Walk(fn func(error))

Walk traverses the error chain, applying fn to each error. Stops if fn is nil or the chain ends. Example:

err.Walk(func(e error) { fmt.Println(e.Error()) })

func (*Error) With

func (e *Error) With(keyValues ...interface{}) *Error

With adds key-value pairs to the error's context and returns the error. Uses a fixed-size array (smallContext) for up to contextSize items, then switches to a map. Thread-safe. Accepts variadic key-value pairs. Example:

err := err.With("key1", value1, "key2", value2)

func (*Error) WithCategory

func (e *Error) WithCategory(category ErrorCategory) *Error

WithCategory sets the error’s category and returns the error. Example:

err := err.WithCategory("validation")

func (*Error) WithCode

func (e *Error) WithCode(code int) *Error

WithCode sets an HTTP-like status code and returns the error. Example:

err := err.WithCode(400)

func (*Error) WithName

func (e *Error) WithName(name string) *Error

WithName sets the error’s name and returns the error. Example:

err := err.WithName("AuthError")

func (*Error) WithRetryable

func (e *Error) WithRetryable() *Error

WithRetryable marks the error as retryable in its context and returns the error. Example:

err := err.WithRetryable()

func (*Error) WithStack

func (e *Error) WithStack() *Error

WithStack captures a stack trace if none exists and returns the error. Skips one frame (caller of WithStack). Example:

err := errors.New("failed").WithStack()

func (*Error) WithTemplate

func (e *Error) WithTemplate(template string) *Error

WithTemplate sets a message template and returns the error. Used as a fallback if the message is empty. Example:

err := err.WithTemplate("operation failed")

func (*Error) WithTimeout

func (e *Error) WithTimeout() *Error

WithTimeout marks the error as a timeout error in its context and returns the error. Example:

err := err.WithTimeout()

func (*Error) Wrap

func (e *Error) Wrap(cause error) *Error

Wrap associates a cause error with this error, creating a chain. Returns the error unchanged if cause is nil. Example:

err := errors.New("failed").Wrap(errors.New("cause"))

func (*Error) WrapNotNil

func (e *Error) WrapNotNil(cause error) *Error

WrapNotNil wraps a cause error only if it is non-nil and returns the error. Example:

err := err.WrapNotNil(maybeError)

func (*Error) Wrapf

func (e *Error) Wrapf(cause error, format string, args ...interface{}) *Error

Wrapf wraps a cause error with formatted message and returns the error. If cause is nil, returns the error unchanged. Example:

err := errors.New("base").Wrapf(io.EOF, "read failed: %s", "file.txt")

type ErrorCategory

type ErrorCategory string

ErrorCategory is a string type for categorizing errors (e.g., "network", "validation").

type ErrorFormatter

type ErrorFormatter func([]error) string

ErrorFormatter defines a function for custom error message formatting. Takes a slice of errors and returns a single formatted string.

type ErrorOpts

type ErrorOpts struct {
	SkipStack int // Number of stack frames to skip when capturing the stack trace.
}

ErrorOpts provides options for customizing error creation.

type ErrorPool

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

ErrorPool is a high-performance, thread-safe pool for reusing *Error instances. Reduces allocation overhead by recycling errors; tracks hit/miss statistics.

func NewErrorPool

func NewErrorPool() *ErrorPool

NewErrorPool creates a new ErrorPool instance. Initializes the pool with a New function that returns a fresh *Error with default smallContext.

func (*ErrorPool) Get

func (ep *ErrorPool) Get() *Error

Get retrieves an *Error from the pool or creates a new one if pooling is disabled or pool is empty. Resets are handled by Put; thread-safe; updates hit/miss stats when pooling is enabled.

func (*ErrorPool) Put

func (ep *ErrorPool) Put(e *Error)

Put returns an *Error to the pool after resetting it. Ignores nil errors or if pooling is disabled; preserves stack capacity; thread-safe.

func (*ErrorPool) Stats

func (ep *ErrorPool) Stats() (hits, misses int64)

Stats returns the current pool statistics as hits and misses. Thread-safe; uses atomic loads to ensure accurate counts.

type ExponentialBackoff

type ExponentialBackoff struct{}

ExponentialBackoff provides an exponentially increasing delay for retry attempts.

func (ExponentialBackoff) Backoff

func (e ExponentialBackoff) Backoff(attempt int, baseDelay time.Duration) time.Duration

Backoff returns a delay that doubles with each attempt, starting from the base delay. Uses bit shifting for efficient exponential growth (e.g., baseDelay * 2^(attempt-1)).

type LinearBackoff

type LinearBackoff struct{}

LinearBackoff provides a linearly increasing delay for retry attempts.

func (LinearBackoff) Backoff

func (l LinearBackoff) Backoff(attempt int, baseDelay time.Duration) time.Duration

Backoff returns a delay that increases linearly with each attempt (e.g., baseDelay * attempt). Implements BackoffStrategy with linear progression.

type MultiError

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

MultiError represents a thread-safe collection of errors with enhanced features. Supports limits, sampling, and custom formatting for error aggregation.

func NewMultiError

func NewMultiError(opts ...MultiErrorOption) *MultiError

NewMultiError creates a new MultiError instance with optional configuration. Initial capacity is set to 4; applies options in the order provided.

func (*MultiError) Add

func (m *MultiError) Add(errs ...error)

Add appends an error to the collection with optional sampling, limit checks, and duplicate prevention. Ignores nil errors and duplicates based on string equality; thread-safe.

func (*MultiError) Addf

func (m *MultiError) Addf(format string, args ...interface{})

Addf formats and adds a new error to the collection.

func (*MultiError) Clear

func (m *MultiError) Clear()

Clear removes all errors from the collection. Thread-safe; resets the slice while preserving capacity.

func (*MultiError) Count

func (m *MultiError) Count() int

Count returns the number of errors in the collection. Thread-safe.

func (*MultiError) Error

func (m *MultiError) Error() string

Error returns a formatted string representation of the errors. Returns empty string if no errors, single error message if one exists, or a formatted list using custom formatter or default if multiple; thread-safe.

func (*MultiError) Errors

func (m *MultiError) Errors() []error

Errors returns a copy of the contained errors. Thread-safe; returns nil if no errors exist.

func (*MultiError) Filter

func (m *MultiError) Filter(fn func(error) bool) *MultiError

Filter returns a new MultiError containing only errors that match the predicate. Thread-safe; preserves original configuration including limit, formatter, and sampling.

func (*MultiError) First

func (m *MultiError) First() error

First returns the first error in the collection, if any. Thread-safe; returns nil if the collection is empty.

func (*MultiError) Has

func (m *MultiError) Has() bool

Has reports whether the collection contains any errors. Thread-safe.

func (*MultiError) IsNull

func (m *MultiError) IsNull() bool

IsNull checks if the MultiError is empty or contains only null errors. Returns true if empty or all errors are null (via IsNull() or empty message); thread-safe.

func (*MultiError) Last

func (m *MultiError) Last() error

Last returns the most recently added error in the collection, if any. Thread-safe; returns nil if the collection is empty.

func (*MultiError) MarshalJSON

func (m *MultiError) MarshalJSON() ([]byte, error)

MarshalJSON serializes the MultiError to JSON, including all contained errors and configuration metadata. Thread-safe; errors are serialized using their MarshalJSON method if available, otherwise as strings.

func (*MultiError) Merge

func (m *MultiError) Merge(other *MultiError)

Merge combines another MultiError's errors into this one. Thread-safe; respects this instance’s limit and sampling settings; no-op if other is nil or empty.

func (*MultiError) Single

func (m *MultiError) Single() error

Single returns nil if the collection is empty, the single error if only one exists, or the MultiError itself if multiple errors are present. Thread-safe; useful for unwrapping to a single error when possible.

func (*MultiError) String

func (m *MultiError) String() string

String implements the Stringer interface for a concise string representation. Thread-safe; delegates to Error() for formatting.

func (*MultiError) Unwrap

func (m *MultiError) Unwrap() []error

Unwrap returns a copy of the contained errors for multi-error unwrapping. Implements the errors.Unwrap interface; thread-safe; returns nil if empty.

type MultiErrorOption

type MultiErrorOption func(*MultiError)

MultiErrorOption configures MultiError behavior during creation.

func WithFormatter

func WithFormatter(f ErrorFormatter) MultiErrorOption

WithFormatter sets a custom error formatting function. Returns a MultiErrorOption for use with NewMultiError; overrides default formatting.

func WithLimit

func WithLimit(n int) MultiErrorOption

WithLimit sets the maximum number of errors to store. Returns a MultiErrorOption for use with NewMultiError; 0 means unlimited, negative values are ignored.

func WithRand

func WithRand(r *rand.Rand) MultiErrorOption

WithRand sets a custom random source for sampling, useful for testing. Returns a MultiErrorOption for use with NewMultiError; defaults to fastRand if nil.

func WithSampling

func WithSampling(rate uint32) MultiErrorOption

WithSampling enables error sampling with a specified rate (1-100). Returns a MultiErrorOption for use with NewMultiError; caps rate at 100 for validity.

type Retry

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

Retry represents a retryable operation with configurable backoff and retry logic. Supports multiple attempts, delay strategies, jitter, and context-aware cancellation.

func NewRetry

func NewRetry(options ...RetryOption) *Retry

NewRetry creates a new Retry instance with the given options. Defaults: 3 attempts, 100ms base delay, 10s max delay, exponential backoff with jitter, and retrying on IsRetryable errors; ensures retryIf is never nil.

func (*Retry) Attempts

func (r *Retry) Attempts() int

Attempts returns the configured maximum number of retry attempts. Includes the initial attempt in the count.

func (*Retry) Execute

func (r *Retry) Execute(fn func() error) error

Execute runs the provided function with the configured retry logic. Returns nil on success or the last error if all attempts fail; respects context cancellation.

func (*Retry) ExecuteContext

func (r *Retry) ExecuteContext(ctx context.Context, fn func() error) error

ExecuteContext runs the provided function with retry logic, respecting context cancellation. Returns nil on success or the last error if all attempts fail or context is cancelled.

func (*Retry) Transform

func (r *Retry) Transform(opts ...RetryOption) *Retry

Transform creates a new Retry instance with modified configuration. Copies all settings from the original Retry and applies the given options.

type RetryOption

type RetryOption func(*Retry)

RetryOption configures a Retry instance. Defines a function type for setting retry parameters.

func WithBackoff

func WithBackoff(strategy BackoffStrategy) RetryOption

WithBackoff sets the backoff strategy using the BackoffStrategy interface. Returns a RetryOption; no-op if strategy is nil, retaining the existing strategy.

func WithContext

func WithContext(ctx context.Context) RetryOption

WithContext sets the context for cancellation and deadlines. Returns a RetryOption; retains context.Background if ctx is nil.

func WithDelay

func WithDelay(delay time.Duration) RetryOption

WithDelay sets the initial delay between retries. Returns a RetryOption; ensures non-negative delay by setting negatives to 0.

func WithJitter

func WithJitter(jitter bool) RetryOption

WithJitter enables or disables jitter in the backoff delay. Returns a RetryOption; toggles random delay variation.

func WithMaxAttempts

func WithMaxAttempts(maxAttempts int) RetryOption

WithMaxAttempts sets the maximum number of retry attempts. Returns a RetryOption; ensures at least 1 attempt by adjusting lower values.

func WithMaxDelay

func WithMaxDelay(maxDelay time.Duration) RetryOption

WithMaxDelay sets the maximum delay between retries. Returns a RetryOption; ensures non-negative delay by setting negatives to 0.

func WithOnRetry

func WithOnRetry(onRetry func(attempt int, err error)) RetryOption

WithOnRetry sets a callback to execute after each failed attempt. Returns a RetryOption; callback receives attempt number and error.

func WithRetryIf

func WithRetryIf(retryIf func(error) bool) RetryOption

WithRetryIf sets the condition under which to retry. Returns a RetryOption; retains IsRetryable default if retryIf is nil.

Directories

Path Synopsis
Package main demonstrates basic usage of the errors package from github.com/olekukonko/errors.
Package main demonstrates basic usage of the errors package from github.com/olekukonko/errors.
Package errmgr provides common error definitions and categories for use across applications.
Package errmgr provides common error definitions and categories for use across applications.

Jump to

Keyboard shortcuts

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