
Go stack traces and the errors package - bootload
http://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
======
TheDong
The fact that you need a third party non-standard package just to reliably get
error stacks is ridiculous.

What's worse, is this solution doesn't solve random third-party packages you
depend on all that much since they'll still use the utterly useless std-
library error package or their own variation which won't necessarily be
compatible.

~~~
fauigerzigerk
What bothers me a lot more is that Go has somehow managed to get a trivial but
crucial thing totally wrong: Some errors can only be identified by value and
others can only be identified by type.

There is no general and reliable way to handle specific types of errors.

And there is no way to add instance specific information to errors in a later
version of a package, because that would require using an error type instead
of (or in addition to) the existing well known error value, which breaks
callers.

~~~
rakoo
> And there is no way to add instance specific information to errors in a
> later version of a package, because that would require using an error type
> instead of (or in addition to) the existing well known error value, which
> breaks callers.

Yes you can. Errors are interfaces, not values. As long as you define an
Errror() method your struct behaves as an error, but it can have anything a
struct has, such as specific information.

You should read Dave's previous presentation
([http://dave.cheney.net/paste/gocon-
spring-2016.pdf](http://dave.cheney.net/paste/gocon-spring-2016.pdf)) to
understand a bit more about what's _already_ possible today and why Dave's
package makes it easier.

~~~
fauigerzigerk
Interfaces don't help in this case. Say you have a package called net:

    
    
      var Error = ...
    
      func Get(url string) (result, error) {
        if ... {
          return Error
        } 
      }
    

Users of your package would do something like this

    
    
      r, err := net.Get("http://example.com")
      if err == net.Error {
        //handle this particular error
      }
    

What if you decide that there are in fact different types errors or you want
to return an error code with each error? How would you do that?

~~~
rakoo
You should read Dave's presentation again. Asserting for a type is the wrong
way to do it, your library should instead provide a helper for the specific
parsing you want to do.

So if you want to know if this error is temporary, you should have this,
_inside the net package_ :

    
    
      func IsTemporary(err error) bool {
        // specific parsing
      }
    

Similarly, if you _really_ want an error code:

    
    
      func ErrorCode(err error) int {
        // a type assertion, which is fine because we're inside the net package
        v, ok := err.(ErrorWithStatusCode)
        if ok {
          return v.ErrorCode
        }
      }
    

(Note that in the standard net/http package, the result of an http call is
inside the result, never in the error that is returned, whatever the status of
the http call is)

Basically your package should give all tools so that callers can take a
decision without knowing about internals of the package. Knowing the exact
error code is probably not what a caller wants; what they probably want to
know is whether the error is temporary or fatal, if they can retry with a
chance of success, if they can take counter-measures and if they can't alert
the user.

~~~
fauigerzigerk
I think I have read everything Dave Cheney has ever written about errors. But
you misunderstand what I'm saying.

The standard library is already littered with error values defined on the
package level. Many third party packages emulate that approach.

Once you have done that in package, there is no way to change it. You cannot
add function call specific data to an error after the fact. There is no way to
implement an IsTemporary(error) function once you have clients that compare
any returned error to a package level singleton using ==.

 _> Knowing the exact error code is probably not what a caller wants_

I had that need many times. For instance, I needed to get the SQLSTATE from
database errors. There is a huge number of SQLSTATE codes and
interpreting/classifying them can't be the job of a data access package or
even a DBMS specific driver library.

Also, how would a networking package know what I consider temporary? Some
protocols like HTTP specify some errors to be temporary, but that may not
coincide with what I'm willing to retry or not retry.

------
nickcw
Having been writing Go for 4 years now, I can say that Dave Cheney's package
is exactly what I've been looking for.

The thing that really sells it to me is the being able to wrap the error when
necessary with errors.Wrap and find the underlying cause with errors.Cause.
I've yet to experiment with the new formatting `%+v` but I can see that coming
in useful too.

There have been earlier attempts at something similar (eg
[https://github.com/juju/errors](https://github.com/juju/errors)) but none
with the same clarity of thought.

So thanks for a great package Dave and for taking the time to whittle it down
into the simplest, most elegant thing.

~~~
themartorana
We've been doing this via an in-house errors package for a while. It does
other things, like log request ID with the error for tracing and whatnot, and
uses a bit of code adopted from panicparse [0] to print pretty, easy-on-the-
eyes, quickly-parsable stack traces.

Honestly I don't know why stack traces aren't formatted for quick parsing.
Many thanks to maruel [1] for the inspiration.

[0]
[https://github.com/maruel/panicparse](https://github.com/maruel/panicparse)

[1] [https://github.com/maruel](https://github.com/maruel)

------
buro9
I've been using my own errors package for a while to address a different issue
with errors when building RESTful APIs or web applications... when the error
occurs, the code that touches it first best knows the HTTP status code to
ultimately return.

My package in it's entirety is:

    
    
        package errors
    
        const ErrUnknown = 0
    
        type RESTError struct {
        	HTTPStatus int    `json:"-"`
        	Code       int    `json:"code,omitempty"`
        	Message    string `json:"error"`
        	Err        error  `json:"-"`
        }
    
        func (e *RESTError) Error() string {
        	return e.Message
        }
    
        func New(status int, code int, message string) *RESTError {
        	return &RESTError{HTTPStatus: status, Code: code, Message: message}
        }
    

Doc comments removed just to show the code and nothing else.

When those errors finally get back to my web handlers, a standard renderError
wraps all errors:

    
    
        func renderError(w http.ResponseWriter, err error) int {
        	if e, ok := err.(*errors.RESTError); ok {
        		return render.JSON(w, e.HTTPStatus, e)
        	}
    
        	e = errors.New(http.StatusInternalServerError, errors.ErrUnknown, err.Error())
        	return render.JSON(w, e.HTTPStatus, e)
        }
    
    

I handle all errors as soon as possible and then immediately assign the HTTP
status code that should be returned, the error code to enable an external
system to look up the error (without string parsing), a sanitised error
message (to allow devs to get an idea without looking up the code) and I do
put the original error in the Err so that my log files can contain the raw
underlying error.

It effectively acts as a map between internal errors, and external
communication of errors, and allows the place where the error occurred to say
"this HTTP status". And because the Err is never stringified I am comforted
that internal sensitive data (this is a billing system and unsanitised data
should never be leaked even via error messages) does not get leaked.

That's my goal, to handle the error in the way that allows it to best
communicate to:

1\. HTTP clients via the status

2\. Developers via an error code and sanitised message (which they can use to
communicate to users)

3\. SREs via detail in the secure logs

I found the native errors nice, but not the best for dealing with my 3
audiences.

~~~
oolongCat
Hah, this is very similar to the one I use within my apps.

type AppError struct { Code int Message string InternalMessage string Time
time.Time HttpCode int }

All my errors are handled in a http handler function. Before that these errors
are produced in any functions called by my handler functions.

I concatenate in to the InternalMessage var any error message I want to log,
basically anything I want to show an admin. The message tag will have anything
I want to show a user. Normally separated by a semicolon.

Because of this I can give good errors to my users, and I have enough
information when something goes wrong.

The only negative might be that, the internal error logs are a bit verbose.

~~~
oolongCat
oops. My Bad

the code above should be

    
    
      type AppError struct { 
        Code int 
        Message string 
        InternalMessage string 
        Time time.Time 
        HttpCode int 
      }

------
aboodman
I don't follow the logic of avoiding error types.

If I'm calling `foo.Bar()` directly, I already have a direct dependency on
package `foo`, so what difference does it make to have another one to get
`foo.BarError`?

If I'm calling `foo.Bar()` indirectly, then it's true that checking
`foo.ErrorType` introduces a novel dependency upon `foo`, but it's equally
true that:

a) my direct dependency is frequently going to check and wrap that error in
some other type anyway, and

b) I can still use the interface trick that Dave describes to avoid a
dependency on `foo`.

I don't see why forcing callers to declare helper interfaces every single time
they want to inspect an error is preferable to them only having to do that as
a rare workaround.

------
astrobe_
There are two types of errors: the ones you log, and the ones you report to
the user.

The difference is that the latter has to be translated to be useful (even when
your user understands english).

~~~
idank
What about the ones you retry?

~~~
astrobe_
This part was covered by Cheney already, isn't it?

------
justme24
Link below from google web cache since website is unavailable

------
justme24
[http://webcache.googleusercontent.com/search?q=cache%3Arobot...](http://webcache.googleusercontent.com/search?q=cache%3Arobotwealth.com%2Fmachine-
learning-financial-prediction-david-
aronson%2F&oq=cache%3Arobotwealth.com%2Fmachine-learning-financial-prediction-
david-aronson%2F&aqs=chrome..69i57j69i58.1735j0j4&sourceid=chrome&ie=UTF-8)

