Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Improve error handling throughout backend #50

Merged
merged 15 commits into from
Aug 10, 2016
Merged

Improve error handling throughout backend #50

merged 15 commits into from
Aug 10, 2016

Conversation

zwass
Copy link
Contributor

@zwass zwass commented Aug 9, 2016

WIP of the new standardized format for error reporting.

The proposal is that errors throughout the codebase are reported as
KolideError where possible (with the exception of validation errors which use
validator.ValidationErrors). There is a rudimentary implementation of
ReturnError which will process the error and output public error information.
Private information is also debug logged. Having this central function allows
us to plug in any other error reporting mechanism we would like in the future.

]
}`

assert.JSONEq(t, expect, resp.Body.String())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert unfortunately fails randomly due to the fact that errors in the JSON is an array (and therefore ordered). It is generated from validator.ValidationErrors which is just a typedef for a map, and in Go maps are unordered (and actually intentionally randomized to prevent relying on any ordering).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do something shady like intentionally order the errors only when testing, or we could do some other shady stuff to do an unordered comparison, or we could change errors to a map keyed on field or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*The unordered comparison wouldn't be shady, would just be a hassle.

@marpaia
Copy link
Contributor

marpaia commented Aug 9, 2016

what do you think about moving this into it's own errors.go file?

@zwass
Copy link
Contributor Author

zwass commented Aug 9, 2016

Yeah, it can totally go into it's own file. Eventually should go into it's own package I think, but we need to avoid a naming conflict with the built in errors package.

"net/http"

"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be awesome to see us use http.Request and http.ResponseWriter directly here to remove this library's dependency on gin. That's what I did with the sessions lib and wrote a wrapper function with get the req and resp from the context with c.Request and c.Response

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make ReturnError take an http.ResponseWriter so it's called like ReturnError(c.Writer, err)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's cool. you'll have to set the content type and all that, but as the go proverb goes: "a little copying is better than a little dependency"

https://go-proverbs.github.io/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further thought, what makes this different than any of the other places throughout the codebase that we use c.JSON()? If we ever wanted to migrate off Gin we could change all of the call sites together.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hope is that we'll use the standard library http functionality whenever possible and one day, we'll look at the codebase and say "oh, we actually don't use gin for much" and cut it. But if we continue to use all of it's features, even if we don't realllllly need to, then it will feel like a more daunting task to move away from it in the future IMO.

@zwass zwass mentioned this pull request Aug 9, 2016
@zwass
Copy link
Contributor Author

zwass commented Aug 9, 2016

@marpaia Can you confirm you are good with this API before I start converting the codebase to use it?

}

func (e *KolideError) Error() string {
return e.PublicMessage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the logic with this being PublicMessage vs PrivateMessage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PublicMessage is safe to be returned to the client. PrivateMessage is the detailed error information. Right now we log PrivateMessage at a debug level but we could also send it to a log aggregator or any other analysis.

I should add some docstrings on all this.

@marpaia
Copy link
Contributor

marpaia commented Aug 9, 2016

@zwass shouldn't there be a func New(text string) error ala https://golang.org/pkg/errors/?

@zwass
Copy link
Contributor Author

zwass commented Aug 9, 2016

Yeah, a basic New makes sense. I'd like to encourage returning useful errors, so perhaps it could be func New(publicMessage, privateMessage string) which would default to a 500 status?

@marpaia
Copy link
Contributor

marpaia commented Aug 9, 2016

Perhaps in addition to func New(string) error? Many usages throughout the codebase already called errors.New(string) error so such a method would provide an exact API match, which would make replacing call-sites easier for sure.

@zwass
Copy link
Contributor Author

zwass commented Aug 9, 2016

There are actually only a few places that errors.New is used in the existing codebase. I'd really like to avoid having any errors that don't contain detailed information as it makes debugging so much harder. My intention was to find all the places we were propagating errors and make sure that the useful detailed information was appended.

Another note, we could return the PrivateMessage to the client if the code is running in debug mode. Could make debugging frontend work much easier.

$ ag errors.New
app/server.go
92:     return errors.NewFromError(err, http.StatusBadRequest, "JSON parse error")

app/auth.go
41: return 0, errors.New("No user set")

errors/errors_test.go
17: err := errors.New("Foo error")
34:     ReturnError(c, errors.New("foo"))

sessions/sessions.go
15: ErrNoActiveSession = errors.New("Active session is not present in the database")
19: ErrSessionNotCreated = errors.New("The session has not been created")
23: ErrSessionExpired = errors.New("The session has expired")
27: ErrSessionMalformed = errors.New("The session token was malformed")
208:            return nil, errors.New("Unexpected signing method")

@marpaia
Copy link
Contributor

marpaia commented Aug 9, 2016

Fine with me, if the call sites are minimal and you're going to update them all. Yeah, that's awesome! I added debug state to the config, so it's config.App.Debug

@zwass
Copy link
Contributor Author

zwass commented Aug 9, 2016

Cool, it sounds like there is basic agreement on this, so I'll go about doing the refactoring and incorporate your comments.

@zwass zwass removed the In Progress label Aug 10, 2016
@zwass
Copy link
Contributor Author

zwass commented Aug 10, 2016

@marpaia Please take a look at this when you get a chance. I think I am mostly done with my intended changes so now I need your comments to finish it up.

func DatabaseError(c *gin.Context) {
c.JSON(500, ServerError("Database error"))
}

// UnauthorizedError emits a response that is appropriate in the event that a
// request is received by a user which is not authorized to carry out the
// requested action
func UnauthorizedError(c *gin.Context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we make all of these require an extra string so we can throw in a longer error which can be used as the private message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah I wanted to mention that it would be great if we could refactor the authorization code to use KolideError to put more helpful information in at the site of the error and then eliminate this function. I felt like the scope of this PR kept creeeeping so I just shimmed this for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if so, then this LGTM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also gotta say it's awesome that you immediately zeroed in on my least favorite part of this PR :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, it's what i hate the most about error management at the moment, probably 30% of the errors returned aren't even the correct error. needs love for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted in #57.

@zwass zwass changed the title Standardized error format Improve error handling throughout backend Aug 10, 2016
@zwass zwass merged commit 604e3e4 into kolide:master Aug 10, 2016
@zwass zwass deleted the validation_errors branch September 15, 2016 17:21
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants