GithubHelp home page GithubHelp logo

rotisserie / eris Goto Github PK

View Code? Open in Web Editor NEW
1.5K 18.0 52.0 199 KB

Error handling library with readable stack traces and flexible formatting support πŸŽ†

Home Page: https://pkg.go.dev/github.com/rotisserie/eris

License: MIT License

Makefile 3.11% Go 96.89%
errors error-handling error-logging go golang sentry-integration eris error-traces

eris's People

Contributors

morningvera avatar sarnikowski avatar sum2000 avatar tomsquest 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eris's Issues

eris.As is incompatible with eris.Wrap

Describe the bug
eris.As does not work as intended when wrapping with eris.Wrap()

To Reproduce
Run this program:

package main

import (
	"errors"
	"fmt"

	"github.com/rotisserie/eris"
)

type X struct {
}

func (X) Error() string {
	return "X"
}

func main() {
	original := X{}

	wrap1std := fmt.Errorf("wrap1: %w", original)
	wrap2std := fmt.Errorf("wrap2: %w", wrap1std)

	wrap1eris := eris.Wrap(original, "wrap1")
	wrap2eris := eris.Wrap(wrap1eris, "wrap2")

	var x X
	fmt.Println(errors.As(wrap2std, &x))
	fmt.Println(errors.As(wrap2eris, &x))

	fmt.Println(eris.As(wrap2std, &x))
	fmt.Println(eris.As(wrap2eris, &x))
}

Expected behavior
The program should output true 4 times. Instead it outputs:

true
true
true
false

Go: 1.16.5
Eris: github.com/rotisserie/[email protected]

Desbloquear

Describe the bug

To Reproduce

Expected behavior

Screenshots

Additional context

Add more control over error format

Is your feature request related to a problem? Please describe.
Currently, it is not possible to show/hide a certain part of the error frame. For ex., only show the method name or line number instead of a complete frame.

Describe the solution you'd like
One solution could be to provide some utility methods for the format object, like format.Show(file, method, line) and users can pass format.Show(true, true, false).

Describe alternatives you've considered
Another solution could be to have an object like options and users could pass options.line = false or something like that.

Invalid mod path

Describe the bug
The go.mod file contains the path "github.com/morningvera/eris" instead of "github.com/rotisserie/eris".

To Reproduce
go get github.com/rotisserie/eris

Expected behavior
The module is installed.

Can't us errors.As to identify an custom error which has been wrapped multiple times

When using a custom error and then wrapping that error with eris.Wrap it's not possible to use errors.As to identify if error is made of target, even if Unwrap is implemented.

package main

import (
	"errors"
	"fmt"

	"github.com/rotisserie/eris"
)

type AnError struct {
	Msg string
	Err error
}

func (ae AnError) Error() string {
	return ae.Msg
}

func (ae AnError) Unwrap() error {
	return ae.Err
}

func main() {
	aerror := AnError{Msg: "invalid", Err: eris.New("eris test error")}
	berr := eris.Wrap(aerror, "the error")

	switch {
	case errors.As(berr, &AnError{}):
		fmt.Println("anerror")
	}
}

I expected this code should print "anerror", if I instead wrap the error with fmt.Errorf and %w, it works as expected.

redundant code in example

In the doc comment you have the example code

if eris.Is(err, NotFound) || eris.Cause(err) == NotFound 

I think the second half is redundant. The only way Is returns false is if it reaches the end of the chain, which means that the last error in the chain would already have been compared to NotFound.

Is there a way to indicate a skip level for stack in eris ?

Is your feature request related to a problem? Please describe.
I would like to call eris.Wrap inside a utility Logging function but have it attribute the stack to where my function is called vs where .Wrap is called. (e.g. a typical skip level arg)

Describe the solution you'd like
I would prefer to call eris.Wrap inside mypackage.LogError instead of passing it in. But have eris skip a stack level and report one level above instead of inside my utility function.

So imagine I have mypackage.LogError(eris.Wrap(err, "blah"));
But instead I want to hide the eris.Wrap call inside mypackage.LogError(err, "blah") so that I don't have to litter my code with eris.Wrap everywhere.
I want to tell eris.Wrap to skip the immediate stack frame and record the one above as the frame.

Describe alternatives you've considered
Putting mypackage.LogError(eris.Wrap()) all over my code.

Additional context
Add any other context or screenshots about the feature request here.

8092702796

Is your feature request related to a problem? Please describe.

75(3)5

Describe the solution you'd like))((

((((@))))

Describe alternatives you've considered

Additional context

#- _7_πŸ’€πŸ’€πŸ’€πŸ’€πŸ’€πŸ’€look)blook)

Refactor CI workflows

It probably makes sense to separate the jobs in our current workflows so that we don't have to duplicate them for PRs and merges to main. Example: we could have a lint.yml that defines the lint workflow for both push and pull_request. For the test workflow, we need to figure out how to handle slight differences between the two (e.g., the push case uploads the test coverage results to CodeCov).

Investigate behavior of eris.Is with wrapped target errors

Should it work if any part of the target error chain matches the error? Or should we require it to include all layers of the target chain?

Basically which of these example targets should return true for an error a: b: c?
a: b, a: c, b: a, b: d, d: a: b: c, a: b: c: d, b: c

Add support for error trace filtering

Is your feature request related to a problem? Please describe.
Since the UnWrappedError object stores the complete trace information, it will be cool to have some filtering operations on error traces. This can be a really useful debugging tool

Describe the solution you'd like
Multiple ways to do it. We can allow string matching filters or lambda functions.

Additional context
Exploring some potential use cases might help coming up with a better API

Add support for multiple external errors (errors.Join)

Is your feature request related to a problem? Please describe.

eris.ToJSON & eris.ToString doesn't capture wrapped error stacks after joining multiple errors with standard errors.Join. Even though the data is there.

func main() {
	errOne := eris.New("error one")
	errTwo := eris.New("error two")

	errThree := eris.Wrap(errors.Join(errTwo, errOne), "error three")

	fmt.Println(eris.ToString(errThree, true))
}

// Outputs:
// error three
// 	main.main:/tmp/sandbox2016565250/prog.go:14
// error two
// error one

This is because the eris.Unpack expects the the error to always be a linear chain of eris errors ending with one external error. The un-wrap loop breaks upon finding the first "external error". In the above case being described, the Error chain would be something like this: *eris.rootError -> `*errors.joinError{errs: []error{*eris.rootError, *eris.rootError}}.

Describe the solution you'd like

Update the eris.Unpack to expect multiple external errors in the chain of eris errors.

something like this:

// UnpackV2 returns a human-readable UnpackedError type for a given error.
func UnpackV2(err error) UnpackedErrorV2 {
	var upErr UnpackedErrorV2
	for err != nil {
		switch err := err.(type) {
		case *rootError:
			upErr.ErrChain = append(upErr.ErrChain, ErrRoot{
				Msg:  err.msg,
				Stack: err.stack.get(),
			})
		case *wrapError:
			// prepend links in stack trace order
			upErr.ErrChain = append(upErr.ErrChain, ErrLink{
					Msg:  err.msg,
					Frame: err.frame.get(),
			})
		default:
			upErr.ErrChain = append(upErr.ErrChain, ErrExternal(err))
		}
		err = Unwrap(err)
	}
	return upErr
}

type UnpackedErrorV2 struct {
	ErrChain    []interface{
		errType() string
		formatJSON() map[string]interface{}
		formatStr() string
	}
}

Describe alternatives you've considered

I've considered following feature request, although the blast radius seems too big in that so I was trying to imagine a smaller and backward-compatible way of doing this.

#101

Additional context

Add eris.As(...)

For completion, we should offer an As method like the one in Go 1.13 errors (https://golang.org/pkg/errors/#As). We should try to make this more reliable than Go's version by preventing panics: "As will panic if target is not a non-nil pointer to either a type that implements error, or to any interface type." Seems like we could just return false in these cases instead.

Consider adding FromString/FromJSON methods

Is your feature request related to a problem? Please describe.
I'm using a message queue that utilizes gob/encoding for encoding arbitrary data into messages. I have messages that need to persist errors between various points of interaction. I would like a simple way of including errors in this, while maintaining all the stack traces and various benefits eris provides.

Describe the solution you'd like
Provide a companion API to eris.ToJSON and eris.ToString (or at least just JSON). Ideally, these functions would take the output of eris.ToJSON and when passed into the new eris.FromJSON, we get back an error (that implements the interface) and has all the relevant stack/external info from wrapping errors.

Alternatively, you could add a companion method to eris.Unpack called eris.Pack that would take an UnpackedError and return a regular error, again keeping all the information.

Describe alternatives you've considered
Manually JSON encoding/decoding, however after crossing this boundary, no code can access the eris functions like eris.Cause, which are very useful for unwrapping.

Thanks for considering.

Unwrapping Opaque errors

Have an is() method for Unwrap to check for opaqueness

Ways to error match

  1. Deep string search
  2. Simple strings contain
  3. String split based on message formatter

wrapPCs might be checked before try to read values from there. panic: runtime error: index out of range [0] with length 0

Describe the bug
while using Wrap or Wrapf, wrapPCs stack array must be checked before getting values from it. it is not always populated, for instance, if you are in a go routine, then you have no stack trace there. I put a code snippet for that.

To Reproduce
Steps to reproduce the behavior:

  1. run the below go code
package main

import (
	"fmt"
	"time"

	"github.com/rotisserie/eris"
)

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()

	go func() {
		err := dummyStack()
		fmt.Println(eris.Wrap(err, "my wrap func").Error())
	}()

	time.Sleep(1 * time.Second)
}

func dummyStack() error {
	return eris.New("my little tiny error")
}

  1. See error

Expected behavior
stack operation needs to be enhanced. So, when you call Wrap in the go routine or main go routine, there is no stack to use in wrap.
code steps are:
https://github.com/rotisserie/eris/blob/master/eris.go#L69
https://github.com/rotisserie/eris/blob/master/stack.go#L90

and boom

Screenshots

panic: runtime error: index out of range [0] with length 0

goroutine 18 [running]:
github.com/rotisserie/eris.(*stack).insertPC(0xc000102000, 0xc000100200, 0x0, 0x40)
        /go/pkg/mod/github.com/rotisserie/[email protected]/stack.go:90 +0x303
github.com/rotisserie/eris.wrap(0x10f3420, 0xc000106030, 0xc000108020, 0xc, 0xc, 0xc000106030)
        /go/pkg/mod/github.com/rotisserie/[email protected]/eris.go:69 +0x351
github.com/rotisserie/eris.Wrap(0x10f3420, 0xc000106030, 0x10d5d1d, 0xc, 0x0, 0x0)
        /go/pkg/mod/github.com/rotisserie/[email protected]/eris.go:38 +0xa3
main.main.func2()
        /go/cmd/playground.go:19 +0x67
created by main.main
        /go/cmd/playground.go:17 +0x57
exit status 2

Desktop (please complete the following information):

  • OS: [iOS]

Additional context
before inserting stack, it might be good to check have a stack first, but i don't know how will change stack formating

// insertPC inserts a wrap error program counter (pc) into the correct place of the root error stack trace.
func (s *stack) insertPC(wrapPCs stack) {
	if len(wrapPCs) == 1 {
		// append the pc to the end if there's only one
		*s = append(*s, wrapPCs[0])
		return
	}
	for at, f := range *s {
////===>> we don't know we have a stack at wrapPC ???
 		if f == wrapPCs[0] {
			// break if the stack already contains the pc
			break
		} else if f == wrapPCs[1] {
			// insert the first pc into the stack if the second pc is found
			*s = insert(*s, wrapPCs[0], at)
			break
		}
	}
}

ToString panics

Describe the bug
eris.ToString panics. Latest master.
BTW, eris.ToString isn't available in latest released version of library, that's why I've switched to master.

To Reproduce

func main() {
	err := eris.New("oops")
	err = eris.Wrap(err, "oops 2")

	fmt.Println(eris.ToString(err, true))
}

Expected behavior
Stacktrace.

Actual behavior

$ go run main.go
panic: runtime error: index out of range [1] with length 1

goroutine 1 [running]:
github.com/rotisserie/eris.(*Stack).insertFrame(0xc00007ec28, 0xc0000a40f0, 0x1, 0x1)
	/Users/dobegor/go/pkg/mod/github.com/rotisserie/[email protected]/stack.go:25 +0x805
github.com/rotisserie/eris.(*UnpackedError).unpackWrapErr(0xc00007ec18, 0xc0000a40c0)
	/Users/dobegor/go/pkg/mod/github.com/rotisserie/[email protected]/format.go:234 +0x2b4
github.com/rotisserie/eris.Unpack(0x111e300, 0xc0000a40c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/Users/dobegor/go/pkg/mod/github.com/rotisserie/[email protected]/format.go:51 +0x168
github.com/rotisserie/eris.ToCustomString(0x111e300, 0xc0000a40c0, 0x1, 0x10ff8b7, 0x1, 0x10ff8b8, 0x1, 0x10ff8ab, 0x1, 0x10ff8b7, ...)
	/Users/dobegor/go/pkg/mod/github.com/rotisserie/[email protected]/format.go:73 +0x50
github.com/rotisserie/eris.ToString(0x111e300, 0xc0000a40c0, 0x10ffd01, 0x6, 0x111e300)
	/Users/dobegor/go/pkg/mod/github.com/rotisserie/[email protected]/format.go:113 +0x1b3
main.main()
	/Users/dobegor/coding/email/main.go:163 +0xbe
exit status 2

Extend external error with stacktrace but keep the context information that error might have

When eris.Wrap is used on an external error to add a stacktrace, only its error message is preserved. The external error might already contain context information which is lost.

Possible solution:
Add a field of type error to eris.rootError and return its value on Unwrap

Alternative:
Expose funcs for creating a stacktrace to add an eris.Stack to the external error type (only applicable for owned types)

Provide getters for StackTrace File, Line and Method

Is your feature request related to a problem? Please describe.
Currently, file, line, and method names are inconvenient to access UnpackedError object. It will be useful to have getters for some of these.

Describe the solution you'd like
API can be

  • getFile()
  • getLine()
  • getRootError()
  • getErrorChain()

Consider adding "debug" information to root errors

Occasionally, it could be a good idea to create a new error with a type and some debug info (e.g. eris.New(type string, info string), eris.New("error not found", "sql no rows")). This will make comparing errors to error not found easy while also letting users add debug context to new errors (which could be shown or not based on a format option). This could also make it easier to send debug info to logs while hiding debug info from clients.

wrap errors with structured data

Is your feature request related to a problem? Please describe.

When I'm wrapping an error, I often have some interesting data that I'd like to include in the wrapping that can be programmatically accessed later. For example: I have an error produced in the process of doing some I/O, and I want to return an error that both wraps that error and also includes some information about the larger operation I was trying to perform, so that the caller can either respond in some way it deems appropriate, or log some structured data that can be easily retrieved later, or so on. I want stack traces to continue to be generated as if I were just wrapping with a string.

Describe the solution you'd like

I'm thinking of adding a Wraps (s for structured) that takes an error. The provided error will be used by the wrapError to:

  • generate Error() output.
  • proxy Is(), As() (if I return eris.Wraps(err, &MyError{...}), then that returned error should yield true for `eris.As(err, &MyError{}). similarly).

Unwrap() will remain unchanged so that traversing the causal chain remains functional.

Describe alternatives you've considered

  • Wrap with a string containing some structured data.
    • this makes the default action (sending to an error log) unwieldy and requires callers to parse it, and complicates distribution of structural knowledge.
  • Do the wrapping myself
    • breaks stack trace reporting
  • Embed some eris-provided type that allows eris to maintain its data within my error
    • This is much more intrusive to the user.

**Additional context

None, I think. I started prototyping this out but am as yet unsure if my current approach will prove satisfactory, but I thought to leave a note here early to gather any feedback.

Discord link unusable

This isn't a bug report.
Discord link isn't usable because there are no channels (that one can view after joining).

image

Provide a Cause() method

Sometimes errors can have multiple types which makes the root cause slightly ambiguous. The Is() method doesn't really solve this because it'll return true for any error type that appears in the chain. Would be nice to have a method that returns the cause of the error. Is it just the first error in the chain?

Kenia✨

Describe the bug

To Reproduce

Expected behavior

Screenshots

Additional context

Add examples / tests

This is separate from adding examples in the docs. We should maintain some examples in error_test.go.

Unpack external errors (especially coming from pkg/errors)

Is your feature request related to a problem? Please describe.
When getting errors from other libraries that use github.com/pkg/errors or forks of it, I noticed that the error is just flattened by calling error.Error() and then returning a rootError.
This is fine for custom errors, but pkg/errors is very widespread and integration with it would make the created errors more complete.

Describe the solution you'd like
It would be nice if eris tries to unpack the error instead of just turning it into string, by recursively calling Unpack. As pkg/errors is very popular, checking for the old func Cause() error in addition to func Unpack() error would be great.

Not just errors from pkg/errors but any error in general that implements Unpack makes sense to unpack in my opinion.

Not sure how to unmangle the stack trace though.

Describe alternatives you've considered

Additional context

all tests are failing, if using gollvm

Describe the bug
All tests are failing, if using gollvm .

To Reproduce
Steps to reproduce the behavior:

  1. Go to my Google Drive download link, for the "release" build and download it
  2. Extract in any custom folder, enter that folder and run

$sudo cp -R * /usr/

  1. Check that you are using a proper Golang compiler

$go version

  1. Get the same error:

$ go test ./...
--- FAIL: TestGlobalStack (0.00s)
stack_test.go:165: eris_test.TestLocalStack: expected number of root error frames { 5 } got { 1 }
--- FAIL: TestGoRoutines (0.25s)
stack_test.go:151: eris_test.TestLocalStack: expected wrap func name { eris_test.TestGoRoutines.func1 } got { eris_test.TestGoRoutines..func1 }
FAIL
FAIL github.com/rotisserie/eris 0.732s
FAIL

Expected behavior
Probably I expected that (at least some) tests would succeed.

Screenshots
If applicable, add screenshots to help explain your problem.
VirtualBox_ubuntu_01_10_2020_16_34_46

Desktop (please complete the following information):

  • OS: Ubuntu x86_64
  • Version 20

Additional context
I was able to run your benchmarks:

$ make bench
Running benchmark tests
goos: linux
goarch: amd64
pkg: github.com/rotisserie/eris/benchmark
BenchmarkWrap/std_errors_1_layers-2 1383235 826 ns/op 88 B/op 4 allocs/op
BenchmarkWrap/pkg_errors_1_layers-2 41601 26564 ns/op 3760 B/op 11 allocs/op
BenchmarkWrap/eris_1_layers-2 26457 43931 ns/op 7960 B/op 25 allocs/op
BenchmarkWrap/std_errors_10_layers-2 155646 7690 ns/op 1056 B/op 31 allocs/op
BenchmarkWrap/pkg_errors_10_layers-2 8312 151270 ns/op 20897 B/op 74 allocs/op
BenchmarkWrap/eris_10_layers-2 4574 240630 ns/op 41155 B/op 88 allocs/op
BenchmarkWrap/std_errors_100_layers-2 7444 134451 ns/op 52054 B/op 301 allocs/op
BenchmarkWrap/pkg_errors_100_layers-2 888 1366197 ns/op 192272 B/op 704 allocs/op
BenchmarkWrap/eris_100_layers-2 433 2489635 ns/op 373100 B/op 718 allocs/op
BenchmarkWrap/std_errors_1000_layers-2 213 5172471 ns/op 5263831 B/op 3002 allocs/op
BenchmarkWrap/pkg_errors_1000_layers-2 94 14191480 ns/op 1906061 B/op 7005 allocs/op
BenchmarkWrap/eris_1000_layers-2 21 52687659 ns/op 3692592 B/op 7020 allocs/op
BenchmarkFormat/std_errors_1_layers-2 3405969 364 ns/op 32 B/op 1 allocs/op
BenchmarkFormat/pkg_errors_1_layers-2 1527482 770 ns/op 96 B/op 3 allocs/op
BenchmarkFormat/eris_1_layers-2 159298 7606 ns/op 920 B/op 23 allocs/op
BenchmarkFormat/std_errors_10_layers-2 2634273 438 ns/op 96 B/op 1 allocs/op
BenchmarkFormat/pkg_errors_10_layers-2 299623 4335 ns/op 1056 B/op 21 allocs/op
BenchmarkFormat/eris_10_layers-2 35584 28518 ns/op 6336 B/op 95 allocs/op
BenchmarkFormat/std_errors_100_layers-2 778509 1550 ns/op 1024 B/op 1 allocs/op
BenchmarkFormat/pkg_errors_100_layers-2 14496 79682 ns/op 52259 B/op 201 allocs/op
BenchmarkFormat/eris_100_layers-2 1969 650065 ns/op 371932 B/op 815 allocs/op
BenchmarkFormat/std_errors_1000_layers-2 116500 10131 ns/op 10240 B/op 1 allocs/op
BenchmarkFormat/pkg_errors_1000_layers-2 183 6927381 ns/op 5265777 B/op 2003 allocs/op
BenchmarkFormat/eris_1000_layers-2 20 50511730 ns/op 35846958 B/op 8018 allocs/op
BenchmarkStack/pkg_errors_1_layers-2 33230 34056 ns/op 2256 B/op 49 allocs/op
BenchmarkStack/eris_1_layers-2 56469 20795 ns/op 4760 B/op 67 allocs/op
BenchmarkStack/pkg_errors_10_layers-2 5378 207628 ns/op 12223 B/op 265 allocs/op
BenchmarkStack/eris_10_layers-2 19087 62277 ns/op 18516 B/op 202 allocs/op
BenchmarkStack/pkg_errors_100_layers-2 256 4283942 ns/op 122995 B/op 2425 allocs/op
BenchmarkStack/eris_100_layers-2 978 1490096 ns/op 855978 B/op 1553 allocs/op
BenchmarkStack/pkg_errors_1000_layers-2 2 936693294 ns/op 577890956 B/op 28246 allocs/op
BenchmarkStack/eris_1000_layers-2 14 97293242 ns/op 79552572 B/op 15096 allocs/op
BenchmarkJSON/eris_1_layers-2 15138 73616 ns/op 6885 B/op 111 allocs/op
BenchmarkJSON/eris_10_layers-2 4846 215716 ns/op 26201 B/op 403 allocs/op
BenchmarkJSON/eris_100_layers-2 523 2267172 ns/op 507676 B/op 3289 allocs/op
BenchmarkJSON/eris_1000_layers-2 18 94480986 ns/op 34654235 B/op 32142 allocs/op
PASS
ok github.com/rotisserie/eris/benchmark 65.102s

CC @thanm @cherrymui for any gollvm related issues

Implement new API (from errgroup and multierror) so eris can become ultimate error package.

Is your feature request related to a problem? Please describe.

Implement new API to have only one error package to manage all common errors related cases/problems.
With this change, I don't have to import other errors packages and eris would become the ultimate solution for almost
all error handling cases.

Describe the solution you'd like

Add/Import more functions and functionalities to this pkg. I've prepared a go docs:

  // Wraps adds additional context to all error types while maintaining the type of the original error.
  //
  // This is a convenience method for wrapping errors with stack trace and is otherwise the same as Wrap(err, "").
  func Wraps(err error) error

  // Append is a helper function that will append more errors onto an Error in order to create a larger multi-error.
  func Append(err error, errs ...error) error

  // WrappedErrors returns the list of errors that this Error is wrapping.
  func WrappedErrors(err error) []error

  // Group is a collection of goroutines that returns errors that need to be coalesced.
  type Group struct {
    // if true group will continue execution until the last goroutine is finished.
    // It will return multierror in case of more than one error, which can be 
    // turn into the slice with WrappedErrors.
    ContinueOnError bool
  }

  // WithContext returns a new Group and an associated Context derived from ctx.
  func WithContext(ctx context.Context) (*Group, context.Context)

  // Go calls the given function in a new goroutine. It can work in two modes
  //
  // 1. The first call to return a non-nil error cancels the group
  //
  // 2. If the function returns an error it is added to the
  // group multierror which is returned by Wait.
  //
  // You can control this behavour by setting ContinueOnError in Group.
  func (g *Group) Go(f func() error)

  // Wait blocks until all function calls from the Go method have returned, then returns either
  // the first non-nill error (if any) or multierror. This depends on ContinueOnError setting.
  func (g *Group) Wait() error

Describe alternatives you've considered

Still using other packages like github.com/hashicorp/go-multierror or golang.org/x/sync/errgroup

Additional context

I know maybe this Issue should be split into smaller ones but I thought it would be easier to gather all this into one Issue.

One real example that shows how multerror and Wraps can be used.

// This is a version with comments. Below you can find two versions to compare readability.
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        // just save a stacktrace without any message
        // it would be nice to have a function like pkgerros WithStack()
        // maybe with shorter name (eris.Stack() or eris.Wraps()),
        // to tell that I want to capture only a stack without any message.
        // right now I use Wrap for this.
        return eris.Wrap(err, "")
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            // "github.com/hashicorp/go-multierror"
            // It would be great to have api to combine
            // multiple errors into one. Ofc I can use
            // eris.Wrap here but it seems a bit overkill
            // to have two calls in stacktrace when in fact
            // I just need two error messages combined into one.
            // This can be used also in many other places
            err = multierror.Append(err, rerr)
        }

        return eris.Wrap(err, "") // eris.Wraps()
    }

    return eris.Wrap(tx.Commit(ctx), "") // eris.Wraps())
}

// This is version with external package and Wrap
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        return eris.Wrap(err, "")
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            err = multierror.Append(err, rerr)
        }

        return eris.Wrap(err, "")
    }

    return eris.Wrap(tx.Commit(ctx), "")
}

// This is version with eris only.
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        return eris.Wraps(err)
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            err = eris.Append(err, rerr)
        }

        return eris.Wraps(err)
    }

    return eris.Wraps(tx.Commit(ctx))
}

Wrap an external customized error end up as a rootErr ?

according to the document of Wrap(),

// Wrap adds additional context to all error types while maintaining the type of the original error.
//
// This method behaves differently for each error type. For root errors, the stack trace is reset to the current
// callers which ensures traces are correct when using global/sentinel error values. Wrapped error types are simply
// wrapped with the new context. For external types (i.e. something other than root or wrap errors), this method
// attempts to unwrap them while building a new error chain. If an external type does not implement the unwrap
// interface, it flattens the error and creates a new root error from it before wrapping with the additional
// context.

it says for external types, it attempts to unwrap the (the unwrap interface), but I didn't find the Unwrap() is called

btw, what is the best practice to wrap an external error, say
type MyError struct {
Code int // error code
}

Global stack trace can be incorrect if another error is wrapped before the first one is logged

Describe the bug
Because error wrapping just wraps a ptr to the global val, we can see errors in the stack traces if multiple errors are wrapped before printing or logging. Basically every time the global is wrapped, it ends up resetting the stack for every error that wrapped it before.

To Reproduce
Observe that the root stack trace for err1 is incorrect in this case. It will be the same as err2 (apart from the inserted wrap frame).

err1 := eris.Wrap(ErrBadRequest, "test 1")
err2 := eris.Wrap(ErrBadRequest, "test 2")
fmt.Printf("%+v\n", err1)
fmt.Printf("%+v\n", err2)

Expected behavior
Wrapping global vals should produce consistent stack traces for all errors. We probably need to deep copy the global so that wrapping it produces an error that can't be interfered with.

Missing error chain trace in Sentry

When logging to Sentry, error chain trace is missing. Text representation of wrapped errors is there, but no File:Location trace.
Using provided example:

go run examples/sentry/example.go -dsn=<Valid Sentry DSN>

Results in Sentry FULL mode:

EXCEPTION(most recent call first)

*eris.rootError
test
*eris.wrapError
wrap 1: test
*eris.wrapError
wrap 2: wrap 1: test
*eris.wrapError
wrap 3: wrap 2: wrap 1: test

example.go in main at line 54
	})
	if initErr != nil {
		log.Fatalf("failed to initialize Sentry: %v", initErr)
	}
	sentry.CaptureException(err)
	sentry.Flush(time.Second * 5)
}

Results in Sentry RAW mode:

EXCEPTION(most recent call first)

*eris.wrapError: wrap 3: wrap 2: wrap 1: test
  File "example.go", line 54, in main
    sentry.CaptureException(err)

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.