Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fire all hooks, even in case of an error #1019

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

flimzy
Copy link
Contributor

@flimzy flimzy commented Sep 8, 2019

The current behavior aborts logging with an error, the first time a hook returns an error.

For me this meant that a misconfiguration of loggly resulted in sentry not getting logs. This PR changes that behavior, such that all hooks will now be called, even in the case of an error with one of them.

@freeformz
Copy link
Collaborator

The semantics of Fire can't be changed in the v1.X.X release series because that would change the logrus.Hook interface and break everyone who uses Hooks.

It's debatable whether or not it's a good idea to have errors cancel processing the rest of the hooks. As you point out a misconfigured hook or one that starts erroring for a random reason greatly impacts your ability to continue logging properly.

My suggestion would be to extend Hook a little and implement something like*:

type Continuer interface {
  Continue() bool
}

// If the error returned from Fire is a Continuer and Continue() is true then the 
// rest of the error will be logged and the remaining Hooks will be called.

IMO it's important not to remove Fire's error though. Some hooks will filter message/data contents to remove PII info and if those hooks were to fail for some reason, it could be a security incident to log the unfiltered data.

*Modulo naming, which is hard.

@flimzy
Copy link
Contributor Author

flimzy commented Sep 19, 2019

Sorry, I didn't ralize that LevelHooks was part of the public API. I'm also a bit confused as to why it is, since it's documented as an 'internal type'.

I'll see about modifying my PR.

This adds a new multiErr type, to report/log multiple errors.
@flimzy
Copy link
Contributor Author

flimzy commented Sep 19, 2019

I've pushed a new implementation of this PR, which retains the original LevelHooks.Fire() API.

It adds a new, unexported type, to report all errors. If we think others will call LevelHooks.Fire() directly, and may want access to all errors, that type should perhaps be exported. I'm not sure if that's a meaningful use case, though, so maybe wait until someone requests it?

@freeformz
Copy link
Collaborator

Continuing to run the remaining hooks after a hook returned an error would also be a change that is too large, at least IMO, for anything more than a full version release (v1.4.2 -> v2). It's a significant change to the behavior of hooks. And it's a possible security problem (see my comment above).

I think it's necessary for hook authors and/or users of hooks to decide if hook processing should stop after the first error or not. I'm also not convinced one way or the other the best way to go about that.

Related to this there is also #986, which is related, but different.

Maybe something combined that addresses everything detailed in both issues would be workable?

@flimzy
Copy link
Contributor Author

flimzy commented Sep 20, 2019

Thanks for the additional comments.

How would you feel about a configuration option to enable this behavior? I think it would be possible to add a config option to the Logger type to control this behavior. Perhaps FireAllOnError bool, or similar? If you think this is a reasonable change, I can work on adjusting the PR.

If not, I think at minimum it's appropriate to document that the first hook failure will abort logging. I hope that such a doc change PR won't be controversial :)

@freeformz
Copy link
Collaborator

Please do the doc PR. FWIW: I thought it was already documented, but re-reading the godoc it appears that I was wrong.

I feel that a PR to add something like "FireAllHooks", defaulting to false (maintains backwards compatibility), with appropriate documentation detailing default and true behavior is a decent option. It should also play well with #986.

@flimzy
Copy link
Contributor Author

flimzy commented Sep 20, 2019

I've updated this PR now with the new configuration flag (off by default). Please let me know your thoughts.

entry.go Outdated Show resolved Hide resolved
hooks.go Outdated Show resolved Hide resolved
hooks.go Outdated Show resolved Hide resolved
@@ -29,6 +29,11 @@ type Logger struct {
// Flag for whether to log caller info (off by default)
ReportCaller bool

// FireAllHooks indicates that all hooks should be fired, even in case of an
// error. When off (default), when an error occurs firing one of the hooks,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe?

When false (default), the first hook that returns an error will stop any unfired hooks from firing and that error will be returned. When true, all hooks are fired and any errors are collected and returned.

Does this mean that mutliErr should be mentioned and exported?

i.e. I'm asking myself how would I process / handle the errors in the case of true and the documentation doesn't really give me any guidance.

@flimzy
Copy link
Contributor Author

flimzy commented Sep 20, 2019

So I'm digging into the error proposals for Go, and the closest relevant one I think is this. It's only a proposal, and probably won't be accepted as is, at least not for a long time, but it's simple, and I think it might be the right approach. Basically, it suggests implementing a specific interface:

interface {
       Errors() []error
}

I could make multiErr satisfy this interface, without exporting multiErr directly. Then it's just a matter of documenting how to access the multiple errors, probably with a brief example.

@flimzy
Copy link
Contributor Author

flimzy commented Sep 20, 2019

I've updated the PR once again with some changes to multi-error handling. I don't know if any or all of it is a good idea. I welcome your thoughts :)

@freeformz
Copy link
Collaborator

Nice. I like the use of the interface to define the behavior of Errors().
There are a few minor nits I'd like to clean up and I'd like to take a look at supporting go1.13 error wrapping if possible, but they don't need to be done in this PR IMO.

@flimzy
Copy link
Contributor Author

flimzy commented Sep 24, 2019

I'd like to take a look at supporting go1.13 error wrapping if possible

What, specifically, would you like to support here? I added Unwrap() to the multiErr type, but it only returns the first error, since the Go 1.13 errors don't have any explicit support for multiple errors. There's probably more that could be done, but it wasn't obvious to me which direction to take.

@freeformz
Copy link
Collaborator

I think it's worth experimenting with the %w formatting verb as well, but like I said, that isn't something that needs to happen in this PR, IMO.

@stale stale bot added the stale label Feb 26, 2020
@markphelps markphelps removed the stale label Feb 26, 2020
Repository owner deleted a comment from stale bot Feb 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants