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

Add ability for interceptors to handle exceptions in lacinia #101

Merged
merged 6 commits into from Jun 4, 2020

Conversation

lennartbuit
Copy link
Contributor

This change enables the use of standard pedestal error handling for errors raised in the interceptor stack or in lacinia itself. It does so by moving error response generation to the very end of the (outbound) interceptor stack. End users can then inject interceptors prior to it to handle or log exceptions as desired.

As it stands, this is only applicable to the synchronous query executor. I would also like to invoke the same error interceptors with exceptions occurring on the asynchronous path, but am currently unable to figure out how exceptions occurring in channels should be handled in pedestal.

If we do figure that out, we can simplify some of the code here as there are now two error paths (one for sync, one for async).

Besides fixing the asynchronous error handling case, it may also be beneficial to move the default error logging to its own separate interceptor. Our use case will directly send these to Rollbar, so the default logging is not required. Another benefit is that exceptions occurring inside the pedestal interceptors can also be logged.

I have verified that these changes work in our application and am eager to make changes if so desired :)!

(if (instance? Throwable result)
(do (log/error :event :execution-exception
:ex result)
;; TODO: Async exceptions should also go into the error interceptor
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 is the TODO that should be resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When thats done, probably also remove the go in favour of a normal chan. If you have suggestions on the pedestal async stuff, let me know, otherwise I'll invest some time (probably) over the weekend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey there. This has been resolved.

After talking to the Pedestal people, it is indeed correct that this function throwable->ex-info is private, but you can assemble the same information from the context map + which interceptor will assoc the error in the context map. Its sad that we have to duplicate this bit of pedestal functionality, but we do now have the same error handling on the sync- and async paths that behaves exactly as it would when it was pedestal generating the wrapped exception.

(The last fact you can verify, replace the assoc'ing of the error with a throw, and the synchronous test should still succeed)

This change enables the use of standard pedestal error handling for errors
raised in the interceptor stack or in lacinia itself. It does so by moving
response error generation to the very end of the (outbound) interceptor stack.
End users can then inject interceptors prior to it to handle or log exceptions
as desired.

As it stands, this is only applicable to the synchronous query executor. I
would also like to invoke the same error interceptors with exceptions
occurring on the asynchronous path, but am currently unable to figure out how
exceptions occurriing in channels should be handled in pedestal.

If we do figure that out, we can simplify some of the code here as there are
now two error paths (one for sync, one for async).
This change unifies the error handling of async-query-executor-handler and
query-execution-handler. Exceptions raised in either will end up in error
interceptors down the interceptor chain.
@lennartbuit
Copy link
Contributor Author

Merged back the changes you made on master. That means this branch is now adding the error handling to both the 'old' pedestal ns and the 'new' pedestal2 ns. As far as I am concerned, this is ready to go, so please let me know what you think ^^!

Based on the (private) `io.pedestal.interceptor.chain/throwable->ex-info` function of pedestal"
[{::chain/keys [execution-id] :as context} exception interceptor-name stage]
(let [exception-str (pr-str (type exception))
msg (str exception-str " in Interceptor " interceptor-name " - " (.getMessage exception))
Copy link
Member

Choose a reason for hiding this comment

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

Can use ex-message here.

Copy link
Member

@hlship hlship left a comment

Choose a reason for hiding this comment

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

Some nitpicks, and I'm dissapointed that pedestal doesn't make it easier to indicate a failure in an async interceptor ... wouldn't it be neat if you could just return a Throwable? But I think as a whole this is an improvement, as we prefer to use interceptors to customize behavior of lacinia-pedestal.

src/com/walmartlabs/lacinia/pedestal/internal.clj Outdated Show resolved Hide resolved
src/com/walmartlabs/lacinia/pedestal2.clj Outdated Show resolved Hide resolved
src/com/walmartlabs/lacinia/pedestal/internal.clj Outdated Show resolved Hide resolved
@@ -125,19 +133,20 @@
This comes last in the interceptor chain."
(interceptor
{:name ::query-executor
:enter internal/on-enter-query-excecutor}))
:enter (internal/on-enter-query-excecutor ::query-executor :enter)}))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also; I just noticed the typo, let me fix that

@lennartbuit
Copy link
Contributor Author

lennartbuit commented Jun 4, 2020

I'm dissapointed that pedestal doesn't make it easier to indicate a failure in an async interceptor ... wouldn't it be neat if you could just return a Throwable?

Yeah so was I, and that was my line of reasoning as well. It's easy to handle an exception on the sync path, but you don't get the same goodies on the async path. So I really felt like I was doing something wrong... but yeah I asked and no.

Anyhow, applied your suggestions :)!

@hlship hlship merged commit abeff09 into walmartlabs:master Jun 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

2 participants