Drop-in replacement for the standard library errors package and github.com/pkg/errors

Overview

Emperror: Errors Mentioned in Awesome Go

GitHub Workflow Status Codecov Go Report Card Go Version PkgGoDev FOSSA Status

Drop-in replacement for the standard library errors package and github.com/pkg/errors.

This is a single, lightweight library merging the features of standard library errors package and github.com/pkg/errors. It also backports a few features (like Go 1.13 error handling related features).

Standard library features:

  • New creates an error with stack trace
  • Unwrap supports both Go 1.13 wrapper (interface { Unwrap() error }) and pkg/errors causer (interface { Cause() error }) interface
  • Backported Is and As functions

github.com/pkg/errors features:

  • New, Errorf, WithMessage, WithMessagef, WithStack, Wrap, Wrapf functions behave the same way as in the original library
  • Cause supports both Go 1.13 wrapper (interface { Unwrap() error }) and pkg/errors causer (interface { Cause() error }) interface

Additional features:

  • NewPlain creates a new error without any attached context, like stack trace
  • Sentinel is a shorthand type for creating constant error
  • WithStackDepth allows attaching stack trace with a custom caller depth
  • WithStackDepthIf, WithStackIf, WrapIf, WrapIff only annotate errors with a stack trace if there isn't one already in the error chain
  • Multi error aggregating multiple errors into a single value
  • NewWithDetails, WithDetails and Wrap*WithDetails functions to add key-value pairs to an error
  • Match errors using the match package

Installation

go get emperror.dev/errors

Usage

package main

import "emperror.dev/errors"

// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")

// ErrMyError is an error that can be returned from a public API.
type ErrMyError struct {
	Msg string
}

func (e ErrMyError) Error() string {
	return e.Msg
}

func foo() error {
	// Attach stack trace to the sentinel error.
	return errors.WithStack(ErrSomethingWentWrong)
}

func bar() error {
	return errors.Wrap(ErrMyError{"something went wrong"}, "error")
}

func main() {
	if err := foo(); err != nil {
		if errors.Cause(err) == ErrSomethingWentWrong { // or errors.Is(ErrSomethingWentWrong)
			// handle error
		}
	}

	if err := bar(); err != nil {
		if errors.As(err, &ErrMyError{}) {
			// handle error
		}
	}
}

Match errors:

package main

import (
    "emperror.dev/errors"
    "emperror.dev/errors/match"
)

// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")

type clientError interface{
    ClientError() bool
}

func foo() error {
	// Attach stack trace to the sentinel error.
	return errors.WithStack(ErrSomethingWentWrong)
}

func main() {
    var ce clientError
    matcher := match.Any{match.As(&ce), match.Is(ErrSomethingWentWrong)}

	if err := foo(); err != nil {
		if matcher.MatchError(err) {
			// you can use matchers to write complex conditions for handling (or not) an error
            // used in emperror
		}
	}
}

Development

Contributions are welcome! :)

  1. Clone the repository
  2. Make changes on a new branch
  3. Run the test suite:
    ./pleasew build
    ./pleasew test
    ./pleasew gotest
    ./pleasew lint
  4. Commit, push and open a PR

License

The MIT License (MIT). Please see License File for more information.

Certain parts of this library are inspired by (or entirely copied from) various third party libraries. Their licenses can be found in the Third Party License File.

FOSSA Status

Comments
  • error.callers holds on to more memory than needed

    error.callers holds on to more memory than needed

    We were analysing an OOM case and found that errors.callers is holding on to more memory than needed. E.g.

    Given this convoluted testcase
    package main
    
    import (
    
    	// pkgerrs "github.com/pkg/errors"
    	pkgerrs "emperror.dev/errors"
    	"github.com/pkg/profile"
    )
    
    var all = []error{}
    
    // go:noinline
    func foobarbaz() {
    	foobar()
    
    }
    
    // go:noinline
    func foobar() {
    	foo()
    }
    
    // go:noinline
    func foo() {
    	moo()
    }
    
    // go:noinline
    func moo() {
    	mootoo()
    }
    
    // go:noinline
    func mootoo() {
    	all = append(all, pkgerrs.New("foo"))
    }
    
    func main() {
    	defer profile.Start(
    		profile.MemProfile,
    		profile.ProfilePath("."),
    	).Stop()
    
    	count := 10_000_000
    	for i := 0; i < count; i++ {
    		foobarbaz()
    	}
    }
    
    ❯ go run main.go && go tool pprof mem.pprof
    2022/02/23 10:20:33 profile: memory profiling enabled (rate 4096), mem.pprof
    (pprof) top 3
    Showing nodes accounting for 1172.78MB, 95.29% of 1230.71MB total; Dropped 17 nodes (cum <= 6.15MB)
    Showing top 3 nodes out of 11
          flat  flat%   sum%        cum   cum%
     1020.92MB 82.95% 82.95%  1020.92MB 82.95%  emperror.dev/errors.callers
       87.18MB  7.08% 90.04%  1108.10MB 90.04%  emperror.dev/errors.WithStackDepth (inline)
       64.68MB  5.26% 95.29%  1230.67MB   100%  main.mootoo
    
    (pprof) list emperror.dev/errors.callers
    Total: 1.20GB
    ROUTINE ======================== emperror.dev/errors.callers in memleaks/vendor/emperror.dev/errors/stack.go
     1020.92MB  1020.92MB (flat, cum) 82.95% of Total
             .          .     56:func callers(depth int) *stack {
             .          .     57:   const maxDepth = 32
             .          .     58:
      933.54MB   933.54MB     59:   var pcs [maxDepth]uintptr
             .          .     60:
             .          .     61:   n := runtime.Callers(2+depth, pcs[:])
             .          .     62:
       87.39MB    87.39MB     63:   var st stack = pcs[0:n]
             .          .     64:
             .          .     65:   return &st
             .          .     66:}
    

    Here you can see that pcs escapes to heap which is confirmed by

    ❯ go build -gcflags='-m -m' vendor/emperror.dev/errors/stack.go 2>&1 | grep pcs
    vendor/emperror.dev/errors/stack.go:59:6: pcs escapes to heap:
    vendor/emperror.dev/errors/stack.go:59:6:   flow: st = &pcs:
    vendor/emperror.dev/errors/stack.go:59:6:     from pcs (address-of) at vendor/emperror.dev/errors/stack.go:63:20
    vendor/emperror.dev/errors/stack.go:59:6:     from pcs[0:n] (slice) at vendor/emperror.dev/errors/stack.go:63:20
    vendor/emperror.dev/errors/stack.go:59:6:     from st = pcs[0:n] (assign) at vendor/emperror.dev/errors/stack.go:63:6
    vendor/emperror.dev/errors/stack.go:59:6: moved to heap: pcs
    

    This leads to an error holding onto maxDepth = 32 uintptr than n. The fix is to ensure that pcs doesn't escape to heap which can be achieved by the following.

    func callers(depth int) *stack {
    	const maxDepth = 32
    
    	var pcs [maxDepth]uintptr
    
    	n := runtime.Callers(2+depth, pcs[:])
    	st := make(stack, n)
    	copy(st, pcs[:n])
    
    	return &st
    }
    

    With this change the pprof for the testcase above shows a deduction of 75%.

    (pprof) top 3
    Showing nodes accounting for 599.08MB, 90.21% of 664.12MB total
    Dropped 16 nodes (cum <= 3.32MB)
    Showing top 3 nodes out of 11
          flat  flat%   sum%        cum   cum%
      355.93MB 53.59% 53.59%   355.93MB 53.59%  emperror.dev/errors.callers
      145.53MB 21.91% 75.51%   664.08MB   100%  main.mootoo
       97.62MB 14.70% 90.21%   453.55MB 68.29%  emperror.dev/errors.WithStackDepth (inline)
    (pprof) list emperror.dev/errors.callers
    Total: 664.12MB
    ROUTINE ======================== emperror.dev/errors.callers in memleaks/vendor/emperror.dev/errors/stack.go
      355.93MB   355.93MB (flat, cum) 53.59% of Total
             .          .     57:   const maxDepth = 32
             .          .     58:
             .          .     59:   var pcs [maxDepth]uintptr
             .          .     60:
             .          .     61:   n := runtime.Callers(2+depth, pcs[:])
      355.93MB   355.93MB     62:   st := make(stack, n)
             .          .     63:   copy(st, pcs[:n])
             .          .     64:
             .          .     65:   return &st
             .          .     66:}
    
    opened by sthaha 4
  • Reduce memory allocation in callers

    Reduce memory allocation in callers

    This fixes issue #27 by ensuring that pcs array allocated on stack does not escape to heap. See issue1 for details.

    Signed-off-by: Sunil Thaha [email protected]

    opened by sthaha 0
  • Error details that created with `errors.NewWithDetails` are lost after it combined using `errors.Combine`

    Error details that created with `errors.NewWithDetails` are lost after it combined using `errors.Combine`

    Code to reproduce

    errWithDetail := errors.NewWithDetails(
     "errorMessage",
      "key", "value",
    )
    errOther := errors.New("Something")
    
    errors.GetDetails(errWithDetail) // [key, value] -> detail exists
    errors.GetDetails(errors.Combine(errWithDetail, errOther) // [] -> prints empty slice
    
    // also happened here
    errors.Append(errWithDetail, errOther) // []
    

    Is this expected?

    opened by dimasdanz 1
  • match.As not equivalent errors.As

    match.As not equivalent errors.As

    package main
    
    import (
    	"fmt"
    
    	"emperror.dev/errors"
    	"emperror.dev/errors/match"
    )
    
    type (
    	myErrorKind string
    	MyError     struct {
    		kind  myErrorKind
    		cause error
    	}
    )
    
    func (e MyError) Error() string {
    	return fmt.Sprintf("%s: %+v", e.kind, e.cause)
    }
    
    func main() {
    	var (
    		err1 error = MyError{
    			kind:  "my type",
    			cause: errors.Sentinel("some error"),
    		}
    		targetTrueErrors MyError
    		targetTrueMatch  MyError
    	)
    
    	fromErrorsTrue := errors.As(err1, &targetTrueErrors)
    	fromMatchTrue := match.As(&targetTrueMatch).MatchError(err1)
    
    	fmt.Println("Expecting true:")
    	fmt.Printf("  From errors: %t\n", fromErrorsTrue)
    	fmt.Printf("  From match : %t\n", fromMatchTrue)
    
    	var (
    		err2              error = errors.Sentinel("some error")
    		targetFalseErrors MyError
    		targetFalseMatch  MyError
    	)
    
    	fromErrorsFalse := errors.As(err2, &targetFalseErrors)
    	fromMatchFalse := match.As(&targetFalseMatch).MatchError(err2)
    
    	fmt.Println("Expecting false:")
    	fmt.Printf("  From errors: %t\n", fromErrorsFalse)
    	fmt.Printf("  From match : %t\n", fromMatchFalse)
    }
    
    // Output:
    // Expecting true:
    //  From errors: true
    //  From match : true
    // Expecting false:
    //  From errors: false
    //  From match : true
    

    I suspect the problem is at this line, but since I'm in a hurry I haven't tried to solve the problem yet

    opened by Fryuni 4
  • Add a marker error

    Add a marker error

    Add an error that allows an error to be marked with a tag/label.

    For example:

    errors.Mark(err, "clientError")
    errors.IsMarked(err, "clientError")
    
    enhancement 
    opened by sagikazarmark 0
Owner
Emperror
Error handling tools and best practices for Go applications
Emperror
A drop-in replacement for Go errors, with some added sugar! Unwrap user-friendly messages, HTTP status code, easy wrapping with multiple error types.

Errors Errors package is a drop-in replacement of the built-in Go errors package with no external dependencies. It lets you create errors of 11 differ

Kamaleshwar 45 Sep 26, 2022
Common juju errors and functions to annotate errors. Based on juju/errgo

errors import "github.com/juju/errors" The juju/errors provides an easy way to annotate errors without losing the original error context. The exporte

Juju 1.3k Nov 26, 2022
Linter for errors.Is and errors.As

erris erris is a program for checking that errors are compared or type asserted using go1.13 errors.Is and errors.As functions. Install go get -u gith

Roman Budnikov 50 Oct 28, 2022
A simple errors package that dynamically prepends the package name.

errors ?? Buy me a cookie What is this? A simple errors package that dynamically prepends the package name. How to install Open a terminal and run the

Miles Whittaker 0 Jan 16, 2022
This structured Error package wraps errors with context and other info

RErr package This structured Error package wraps errors with context and other info. It can be used to enrich logging, for example with a structured l

Rohan Allison 0 Jan 21, 2022
A Go (golang) package for representing a list of errors as a single error.

go-multierror go-multierror is a package for Go that provides a mechanism for representing a list of error values as a single error. This allows a fun

HashiCorp 1.8k Nov 19, 2022
Package semerr helps to work with errors in Golang.

semerr Package semerr helps to work with errors in Golang. Const error An error that can be defined as const. var errMutable error = errors.New("mutab

Maxim Krivchun 5 Oct 30, 2022
A Nostress Errors Package For Golang

A Nostress Errors Package For Golang

null 0 Nov 2, 2021
Go package for errors with chained stack traces

errstack: Go errors with chained stack traces errstack is a Go package for creating errors with stack traces. It is heavily inspired by github.com/pkg

Greg Ward 0 Nov 27, 2021
A Go package providing errors with a stack trace Read-only

Errors with a stack trace A Go package providing errors with a stack trace. Features: Based of github.com/pkg/errors with similar API, addressing many

null 1 Sep 23, 2022
Simple Go library for typed errors creation

go-typed-errors Why this repo was created? Main reason was to create additional methods for better error typing support. Why not to use errors.As and

Bogdan 2 Nov 5, 2021
Simple Go library for typed const errors creation

go-errors Why this repo was created? Main reason was to create additional methods for better error typing support. Features Errors as constants errors

Bogdan 2 Nov 5, 2021
Fault injection library in Go using standard http middleware

Fault The fault package provides go http middleware that makes it easy to inject faults into your service. Use the fault package to reject incoming re

GitHub 470 Nov 20, 2022
eris provides a better way to handle, trace, and log errors in Go 🎆

eris Package eris provides a better way to handle, trace, and log errors in Go. go get github.com/rotisserie/eris Why you'll want to switch to eris Us

null 1.2k Nov 25, 2022
Golang errors with stack trace and source fragments.

Golang Errors with Stack Trace and Source Fragments Tired of uninformative error output? Probably this will be more convenient: Example package main

null 746 Nov 11, 2022
Go tool to wrap and fix errors with the new %w verb directive

errwrap Wrap and fix Go errors with the new %w verb directive. This tool analyzes fmt.Errorf() calls and reports calls that contain a verb directive t

Fatih Arslan 354 Nov 10, 2022
Golang errors with stacktrace and context

merry Add context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values. The package is la

Russ Egan 261 Nov 19, 2022
The Emperor takes care of all errors personally

The Emperor takes care of all errors personally. Go's philosophy encourages to gracefully handle errors whenever possible, but some times recovering f

Emperror 275 Nov 19, 2022
Hierarchical errors reporting done right in Golang

Hierarchical errors made right Hate seeing error: exit status 128 in the output of programs without actual explanation what is going wrong? Or, maybe,

reconquest 74 Nov 9, 2021