GithubHelp home page GithubHelp logo

emperror / errors Goto Github PK

View Code? Open in Web Editor NEW
192.0 192.0 13.0 181 KB

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

Home Page: https://emperror.dev/errors

License: MIT License

Go 95.67% Starlark 1.81% Shell 2.51%
error errors stacktrace

errors's Introduction

Emperror

Mentioned in Awesome Go

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

The Emperor takes care of all errors personally.

Go's philosophy encourages to gracefully handle errors whenever possible, but some times recovering from an error is not.

In those cases handling the error means making the best effort to record every detail for later inspection, doing that as high in the application stack as possible.

This project provides tools to make error handling easier.

Read more about the topic here:

Features

Installation

go get emperror.dev/emperror

Usage

Log errors

Logging is one of the most common target to record error events.

Emperror has two logger integrations by default:

Annotate errors passing through an error handler

Emperror can annotate errors with details as defined in emperror.dev/errors

package main

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

func main() {
	handler := emperror.WithDetails(newHandler(), "key", "value")

	err := errors.New("error")

	// handled error will receive the handler details
	handler.Handle(err)
}

Panics and recovers

package main

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

func main() {
	var handler emperror.Handler = newHandler()

	// Recover from panics and handle them as errors
	defer emperror.HandleRecover(handler)

	// nil errors will not panic
	emperror.Panic(nil)

	// this will panic if foo returns with a non-nil error
	// useful in main func for initial setup where "if err != nil" does not make much sense
	emperror.Panic(foo())
}

func foo() error {
	return errors.New("error")
}

Filter errors

Sometimes you might not want to handle certain errors that reach the error handler. A common example is a catch-all error handler in a server. You want to return business errors to the client.

package main

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

func main() {
	var handler emperror.Handler = emperror.WithFilter(newHandler(), match.Any{/*any emperror.ErrorMatcher*/})

    // errors matching the provided matcher will not be handled
	handler.Handle(err)
}

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.

FOSSA Status

errors's People

Contributors

sagikazarmark avatar sthaha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

errors's Issues

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:}

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")

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.