-
Notifications
You must be signed in to change notification settings - Fork 399
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 context to global error handler #525
Add context to global error handler #525
Conversation
Realized that there is context available at the first call of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'm positive to have more args for the error handler but I'd like to know others' thoughts.
src/App.ts
Outdated
@@ -129,7 +129,7 @@ export interface ViewConstraints { | |||
} | |||
|
|||
export interface ErrorHandler { | |||
(error: CodedError): Promise<void>; | |||
(error: CodedError, middlewareArgs: Object): Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
middlewareArgs
's type can be a new type that consists of AnyMiddlewareArgs
+ context
, logger
, client
(in other words, AllMiddlewareArgs
- next
)
thanks for this PR @raycharius. these use cases are great and these are impactful problems that we need to solve. let's land a change! my apologies for a really long answer here, i just wanted to share as much information as possible with you and the community. i think the API you've built is nice and simple, but i have two specific concerns:
Alternative: Add data as properties of the errorLet's say we want to expose We could add these as properties of the Error type that an ErrorHandler receives instead of adding more parameters to an ErrorHandler. With this in place, one could write a handler using destructuring like this:
An advantage is that if you don't care about a single argument (such as A disadvantage is that accessing the value as an Another disadvantage is that previously written code may all of the sudden have access to potentially sensitive data. If I previously logged the entire |
I am still wanting to move this forward if we can do it without any breaking changes that may affect existing apps. |
Happy to revisit and get this up to par! Per Ankur's comment, I think points are very valid when it comes to the trying to stick to the destructuring convention in middleware, and at the same time, out of the two approaches: app.error((error, { logger, body }) => {
// Log the error with context
}); And: app.error(({ message, logger, body }) => {
// Log the error with context
}); I personally prefer the first. Though it does break the destructuring convention, it definitely won't break any apps, and it's very similar to the way errors are handled in Express middleware: app.use((error, req, res, next) => {
// Log the error with context
}); Adding properties to the error object has some (limited) potential to create problems for existing apps, too. If there are any errors being caught in the middleware that are thrown as new errors with parameters from the request to the global handler. One thing I do think, though, is that perhaps we should limit the middleware args being passed through so that top-level contents of the object is always consistent:
Let me know what you think, and I'll spend some time on in the next day or two, including the suggestion above to type it out a bit better. Cheers! |
Also, just saw you added this to 4.0.0 milestone. Above applicable for smaller updates. If it were in a major, breaking release, I think that Ankur's suggestion is a super great and very Bolt-like way to do it: app.error(({ error, logger, body, context }) => {
// Logging with context
}); |
@raycharius Thanks for your prompt reply here! Let's name the two approaches. A:
B:
Both sounds nice! But, if I have to choose between these, I prefer B than A too. As long as we can provide the new way along with the existing simple one by overloading, I think that we can have the changes in v3 series. The only reason why I set v4.0 as its milestone is that I was thinking we may not work on this in the short term, not for possible breaking changes. |
@seratch Totally agree that B is more ⚡ Bolt-ish and more elegant! What do mean here with overloading in the context of the current handler and JS? Any ideas of how to implement that here without app-level configuration? Within v3, without breaking changes, the only good options I've been able to come up with are approach A, or providing approach B when configured at app initialization: const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
extendErrorHandler: true,
}); |
@raycharius I haven't checked the feasibility yet but I was thinking that it may be possible to accept yet another handler type (say, With this way, we may be able to enhance without asking existing apps to update their code. In the case where |
@seratch Cool, I'll wait for feedback from you then, but from what I can fathom, the function that needs to be overloaded is the function passed into
|
Good point - yes, perhaps you're right. The flag to determine the type of the function argument would be required in JavaScript. |
Happy to put some work into this and the documentation to get this pushed out. What do you think about: const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
extendErrorHandler: true,
}); And if true: app.error(({ error, body, logger }) => {
// Logging magic
}); Including only |
The flag name
I agree these attributes should be included at least. For the rest in the list of possible arguments, we are unable to have
Thank you very much! 🙇♂️ We're currently on bolt-js 3.4 along with web-api 6.2 development. These versions focus on better developer experience in TypeScript and major bugfixes. The timing after completing these releases would be easier for us to focus on this task! |
Sounds good! I'll go ahead and throw in suggested implementation (without documentation) in the next few days, as it's not a big change, so there's a bit more context for the discussion, and from there we can make changes based on any other feedback and comments on implementation. |
Pull latest bolt-js changes to PR
I've revamped the implementation as per our discussions above. Looking forward to hearing your thoughts!
If implementation looks good and this is open for acceptance, I'll go ahead and edit the docs, too. Cheers! |
@raycharius Thank you very much for continuously working hard on this! At the first glance, the latest changes look great to me 👍 If other maintainers have thoughts, I would love to know them 👀
Regarding the timeline, we are still working on 3.4 development. Perhaps, we can merge this pull request into v3.5 release (as long as we don't bring any breaking changes). I cannot tell the schedule for v3.5 / v4 yet but we will update you before long! |
Hi @seratch! Any idea when this might be able to be merged and released? Thanks! |
@raycharius Can you ask you to resolve the conflicts once the v3.7 is released? |
Absolutely! And will also update the docs to reflect the changes once v3.7 has been released – or is that something you like to keep in-house? |
Thanks for saying this! Can you create a new pull request for the document updates (=don't include the document changes in this PR)? We would like to merge the document change after the release including your change.
As the error handling document section is relatively simple, we are happy to work together with you! |
Sounds good. I'll keep an eye out for 3.7.0, then. And once it's out, I'll resolve the conflicts and open a separate PR for the docs. Thanks! |
Codecov Report
@@ Coverage Diff @@
## main #525 +/- ##
==========================================
+ Coverage 71.71% 71.83% +0.12%
==========================================
Files 15 15
Lines 1354 1360 +6
Branches 402 405 +3
==========================================
+ Hits 971 977 +6
Misses 312 312
Partials 71 71
Continue to review full report at Codecov.
|
✅ Conflicts handled I also refactored the types and overloads. The error handler type tests that were introduced after resolving issue #925 brought to light that the original implementation would have caused compilation errors for those using TS and who hadn't explicitly declared the type of the arg in the error handler as Let me know if the changes look good to you, and I'll send over a separate PR with doc updates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raycharius Left one comment. Apart from that, everything looks great to me
src/App.ts
Outdated
@@ -153,10 +154,28 @@ export interface ViewConstraints { | |||
type?: 'view_closed' | 'view_submission'; | |||
} | |||
|
|||
export interface AllErrorHandlerArgs { | |||
error: Error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this type a CodedError
? You call asCodedError
when passing the value in App.ts
L973
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. There are two places where an error is passed to the handler, L749 and L957.
Just above L749, a code is assigned to the error, but at L957, it's not clear whether or not it is a CodedError
. Although it will work since there's the const e = error as any
line in both blocks of code, my thoughts were that it could easily lead to issues down the road, like someone removing the call of the asCodedError
function since the type suggests it is 100% a CodedError
What are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your prompt reply. Both lines call this.handleError
method, which internally calls asCodeError
method to build a CodedError
. In the case where an error does not have a code, asCodedError
still returns UnknownError
, which is a sub type of CodedError
. Thus, we can safely assume that the error
argument of this.errorHandler
method call is always a CodedError
. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AllErrorHandlerArgs
interface is only used for the type of the params received in this.handleError
, before the asCodedError
function is called, whereas ExtendedErrorHandlerArgs
, which extends AllErrorHandlerArgs
(L164) and types error
as a CodedError
is the type for the args passed into the this.errorHandler
method
Maybe the naming needs some work on AllErrorHandlerArgs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export interface ExtendedErrorHandlerArgs extends AllErrorHandlerArgs
Ah ha, now I understand it. Sorry for bothering you here. I overlooked that there are two args types.
Maybe the naming needs some work on AllErrorHandlerArgs?
I think that naming is fine but it seems that AllErrorHandlerArgs
is used only inside App
class. Thus, we can hold off having "export" for the types at this moment. Also, having some comments around the types / methods would be helpful for code readers (like me).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added in some comments, let me know if it is back up to par
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me! Thanks 👍
@raycharius Thanks for working on this change for a long time! Also, we are grateful for your support on the document updates too 🙇 |
@seratch Happy to! It was definitely a long one (with a really poor initial implementation) 😆 I'm going to send over a PR for the docs over the next few days. Was thinking of using the expandable content approach seen in this section or this section. It seems to be the default approach to explain more complex use cases |
Summary
When building an app, it's great to be able to have a central error handler where you can centralize all error handling logic. Similar to the way Express handles error middleware. But without the context of the request, the client, and logger, it becomes difficult and requires workarounds.
Wasn't a big change, so went ahead and opened a PR per the guidelines.
As a Dev, I Want To:
Now passing the same args passed to a listener, to the error handler. Passed in everything, similar to the way Express passes in the entire
req
object to all middleware.I wasn't sure which naming would be appropriate, so I named it
middlewareArgs
, and seeing as the content can be very different from request to request, also typed it as Object. Happy to improve!Requirements (place an
x
in each[ ]
)