3 ms·
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 occu
by buro9 10y ago
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 10y agoHah, 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 10y agooops. My Bad the code above should be type AppError struct { Code int Message string InternalMessage string Time time.Time HttpCode int }
- mkawia 10y agoI have something similar , I've extended the http code by adding a extra digits,it's for business logic errors . So http 4000 is bad request , but in the business logic terms
- zalmoxes 10y agoThe go-kit project has a couple other examples for how to handle errors and http status code. https://github.com/go-kit/kit/blob/master/examples/shipping/booking/transport.go#L175-L178 https://github.com/go-kit/kit/blob/master/examples/shipping/... and more recently https://github.com/go-kit/kit/blob/master/examples/addsvc/transport_http.go https://github.com/go-kit/kit/blob/master/examples/addsvc/tr...
- nzoschke 10y agoThis is great. Thanks for sharing. I have the same exact needs for my APIs so I might borrow this! This technique and the OP show the power of "programming errors" in Go.