errors icon indicating copy to clipboard operation
errors copied to clipboard

WithMessage appends message after associated stack traces instead of before

Open noonat opened this issue 8 years ago • 9 comments

After amending an error using errors.WithMessage, printing the error with %+v prints the extra information after the stack trace instead of with the original message. For example:

package main

import (
	"log"

	"github.com/pkg/errors"
)

func a() error {
	return errors.New("error details")
}

func b() error {
	if err := a(); err != nil {
		return errors.WithMessage(err, "additional details")
	}
	return nil
}

func main() {
	if err := b(); err != nil {
		log.Fatalf("%+v", err)
	}
}

This prints:

2017/02/06 14:34:13 error details
main.a
	.../example.go:10
main.b
	.../example.go:14
main.main
	.../example.go:21
runtime.main
	/usr/local/Cellar/go/1.7.4_2/libexec/src/runtime/proc.go:183
runtime.goexit
	/usr/local/Cellar/go/1.7.4_2/libexec/src/runtime/asm_amd64.s:2086
additional details
exit status 1

As mentioned in #75, it seems like it would be more helpful to include the additional information with original error message. When using %v with the same example, it prints messages in this way now:

2017/02/06 14:33:32 additional details: error details
exit status 1

noonat avatar Feb 06 '17 22:02 noonat

I can confirm the issue. I also expected the full error message at the top.

benma avatar Jun 14 '17 08:06 benma

Sorry, that's how it's designed, the inner most error goes on the top.

On Wed, 14 Jun 2017, 18:06 benma [email protected] wrote:

I can confirm the issue. I also expected the full error message at the top.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/pkg/errors/issues/102#issuecomment-308354212, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAcA74NBfxiMYyvC0UMmEO6GKFM5Y9vks5sD5SFgaJpZM4L4z5i .

davecheney avatar Jun 14 '17 08:06 davecheney

Ok, thanks.

benma avatar Jun 14 '17 09:06 benma

For reference, if anyone finds it useful, this is the function I use to format the stack trace. It puts the full error message on top and removes the quadratic blowup of the stack trace when there are many wraps (see https://github.com/pkg/errors/issues/75#issuecomment-242394764):

func ErrFormat(err error) string {
	if err == nil {
		return ""
	}
	type stackTracer interface {
		StackTrace() errors.StackTrace
	}
	cause := errors.Cause(err)
	if stackTrace, ok := cause.(stackTracer); ok {
		buf := bytes.Buffer{}
		for _, frame := range stackTrace.StackTrace() {
			buf.WriteString(fmt.Sprintf("\n%+v", frame))
		}
		return err.Error() + buf.String()
	}
	return err.Error()
}

benma avatar Jun 14 '17 09:06 benma

This is AWESOME!

It's precisely what I wanted for users of this package.

There is no way that I could choose one format that would fit everyone's needs, so I wanted to provide the raw materials so you could define your own format.

On Wed, Jun 14, 2017 at 7:12 PM, benma [email protected] wrote:

For reference, if anyone finds it useful, this is the function I use to format the stack trace. It puts the full error message on top and removes the quadratic blowup of the stack trace when there are many wraps (see #75 (comment) https://github.com/pkg/errors/issues/75#issuecomment-242394764 ):

func ErrFormat(err error) string { if err == nil { return "" } type stackTracer interface { StackTrace() errors.StackTrace } cause := errors.Cause(err) if stackTrace, ok := cause.(stackTracer); ok { buf := bytes.Buffer{} for _, frame := range stackTrace.StackTrace() { buf.WriteString(fmt.Sprintf("%+v\n", frame)) } return err.Error() + "\n" + buf.String() } return err.Error() }

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pkg/errors/issues/102#issuecomment-308372569, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAcA0-M8PkycAQPLYHhDkjECqIYmn8Fks5sD6QHgaJpZM4L4z5i .

davecheney avatar Jun 14 '17 10:06 davecheney

I am glad you did ;)

Maybe it would be worth it to include a few different formatters in the library so new users don't get any surprises and don't need to spend time doing their own? So far we've identified at least one different one where multiple users have been surprised before, and likely will again, so we could save their time dealing with this.

benma avatar Jun 14 '17 10:06 benma

I don't want to add helper formatter functions because they will begat more and more increasingly undifferentiated formatters.

However, I'm happy to take PRs that add examples of how to write your own formatter.

On Wed, Jun 14, 2017 at 8:50 PM, benma [email protected] wrote:

I am glad you did ;)

Maybe it would be worth it to include a few different formatters in the library so new users don't have get any surprises and don't need to spend time doing their own? So far we've identified at least one different one where multiple users have been surprised before, and likely will again, so we could save their time dealing with this.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pkg/errors/issues/102#issuecomment-308395761, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAcA0DZMyuz7CAVAQnrqFn1W4c6rgnhks5sD7sPgaJpZM4L4z5i .

davecheney avatar Jun 14 '17 10:06 davecheney

I understand. Thanks!

benma avatar Jun 14 '17 11:06 benma

Here's a formatter I just finished...

func printError(w io.Writer, err error) {
	// Messages come back from deepest to shallowest; reverse the order for printing.
	msgs, stack := deconstruct(err)
	for i := range msgs {
		if i > 0 {
			fmt.Fprint(w, "\n...caused by ")
		}
		fmt.Fprint(w, msgs[len(msgs)-i-1])
	}
	if stack != nil {
		fmt.Fprintf(w, "%+v\n", stack)
	}
}

func deconstruct(err error) ([]string, errors.StackTrace) {
	if err == nil {
		return nil, nil
	}

	myMsg := err.Error()
	var messages []string
	var stack errors.StackTrace

	type causer interface {
		Cause() error
	}
	if causer, ok := err.(causer); ok {
		messages, stack = deconstruct(causer.Cause())
		if len(messages) > 0 {
			pos := strings.Index(myMsg, ": "+messages[len(messages)-1])
			if pos >= 0 {
				myMsg = myMsg[:pos]
			}
		}
	}

	messages = append(messages, myMsg)

	if stack == nil {
		type stackTracer interface {
			StackTrace() errors.StackTrace
		}
		if tracer, ok := err.(stackTracer); ok {
			stack = tracer.StackTrace()
		}
	}

	return messages, stack
}

Example output:

You do not appear to be inside of a git repo, this tool is meant to be used in a git repo
...caused by Failed to execute 'git rev-parse --show-toplevel'
...caused by fatal: Not a git repository (or any of the parent directories): .git
main.(*gitter).Command
	/Users/scottb/src/mn/projects/fullstory/go/src/fs/transform/main/repoman/git.go:151
main.run
	/Users/scottb/src/mn/projects/fullstory/go/src/fs/transform/main/repoman/repoman.go:224
main.main
	/Users/scottb/src/mn/projects/fullstory/go/src/fs/transform/main/repoman/repoman.go:111
runtime.main
	/usr/local/Cellar/go/1.7.4/libexec/src/runtime/proc.go:183
runtime.goexit
	/usr/local/Cellar/go/1.7.4/libexec/src/runtime/asm_amd64.s:2086

dragonsinth avatar Jun 30 '17 22:06 dragonsinth