Skip to content

Commit 3e117cb

Browse files
authored
Merge pull request goldbergyoni#843 from goldbergyoni/clarifying-single-error-handler
Clarifying error handling flow
2 parents b6be84e + e4f9f3d commit 3e117cb

File tree

1 file changed

+32
-20
lines changed

1 file changed

+32
-20
lines changed

Diff for: sections/errorhandling/centralizedhandling.md

+32-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### One Paragraph Explainer
44

5-
Without one dedicated object for error handling, greater are the chances of important errors hiding under the radar due to improper handling. The error handler object is responsible for making the error visible, for example by writing to a well-formatted logger, sending events to some monitoring product like [Sentry](https://sentry.io/), [Rollbar](https://rollbar.com/), or [Raygun](https://raygun.com/). Most web frameworks, like [Express](http://expressjs.com/en/guide/error-handling.html#writing-error-handlers), provide an error handling middleware mechanism. A typical error handling flow might be: Some module throws an error -> API router catches the error -> it propagates the error to the middleware (e.g. Express, KOA) who is responsible for catching errors -> a centralized error handler is called -> the middleware is being told whether this error is an untrusted error (not operational) so it can restart the app gracefully. Note that it’s a common, yet wrong, practice to handle errors within Express middleware – doing so will not cover errors that are thrown in non-web interfaces.
5+
Without one dedicated object for error handling, greater are the chances for inconsistent errors handling: Errors thrown within web requests might get handled differently from those raised during the startup phase and those raised by scheduled jobs. This might lead to some types of errors that are being mismanaged. This single error handler object is responsible for making the error visible, for example, by writing to a well-formatted logger, firing metrics using some monitoring product (like [Prometheus](https://prometheus.io/), [CloudWatch](https://aws.amazon.com/cloudwatch/), [DataDog](https://www.datadoghq.com/), and [Sentry](https://sentry.io/)) and to decide whether the process should crash. Most web frameworks provide an error catching middleware mechanism - A typical mistake is to place the error handling code within this middleware. By doing so, you won't be able to reuse the same handler for errors that are caught in different scenarios like scheduled jobs, message queue subscribers, and uncaught exceptions. Consequently, the error middleware should only catch errors and forward them to the handler. A typical error handling flow might be: Some module throws an error -> API router catches the error -> it propagates the error to the middleware (e.g. or to other mechanism for catching request-level error) who is responsible for catching errors -> a centralized error handler is called.
66

77
### Code Example – a typical error flow
88

@@ -30,11 +30,16 @@ catch (error) {
3030

3131
// Error handling middleware, we delegate the handling to the centralized error handler
3232
app.use(async (err, req, res, next) => {
33-
const isOperationalError = await errorHandler.handleError(err);
34-
if (!isOperationalError) {
35-
next(err);
36-
}
33+
await errorHandler.handleError(err, res);//The error handler will send a response
3734
});
35+
36+
process.on("uncaughtException", error => {
37+
errorHandler.handleError(error);
38+
});
39+
40+
process.on("unhandledRejection", (reason) => {
41+
errorHandler.handleError(reason);
42+
});
3843
```
3944
</details>
4045

@@ -62,11 +67,16 @@ catch (error) {
6267

6368
// Error handling middleware, we delegate the handling to the centralized error handler
6469
app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => {
65-
const isOperationalError = await errorHandler.handleError(err);
66-
if (!isOperationalError) {
67-
next(err);
68-
}
70+
await errorHandler.handleError(err, res);
6971
});
72+
73+
process.on("uncaughtException", (error:Error) => {
74+
errorHandler.handleError(error);
75+
});
76+
77+
process.on("unhandledRejection", (reason) => {
78+
errorHandler.handleError(reason);
79+
});
7080
```
7181
</details>
7282

@@ -80,11 +90,10 @@ app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => {
8090
module.exports.handler = new errorHandler();
8191

8292
function errorHandler() {
83-
this.handleError = async (err) => {
84-
await logger.logError(err);
85-
await sendMailToAdminIfCritical;
86-
await saveInOpsQueueIfCritical;
87-
await determineIfOperationalError;
93+
this.handleError = async (error, responseStream) => {
94+
await logger.logError(error);
95+
await fireMonitoringMetric(error);
96+
await crashIfUntrustedErrorOrSendResponse(error, responseStream);
8897
};
8998
}
9099
```
@@ -95,12 +104,11 @@ function errorHandler() {
95104

96105
```typescript
97106
class ErrorHandler {
98-
public async handleError(err: Error): Promise<void> {
99-
await logger.logError(err);
100-
await sendMailToAdminIfCritical();
101-
await saveInOpsQueueIfCritical();
102-
await determineIfOperationalError();
103-
};
107+
public async handleError(err: Error, responseStream: Response): Promise<void> {
108+
await logger.logError(error);
109+
await fireMonitoringMetric(error);
110+
await crashIfUntrustedErrorOrSendResponse(error, responseStream);
111+
};
104112
}
105113

106114
export const handler = new ErrorHandler();
@@ -145,6 +153,10 @@ app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
145153
```
146154
</details>
147155

156+
### Illustration: The error handling actors and flow
157+
![alt text](https://github.com/goldbergyoni/nodebestpractices/blob/master/assets/images/error-handling-flow.png "Error handling flow")
158+
159+
148160
### Blog Quote: "Sometimes lower levels can’t do anything useful except propagate the error to their caller"
149161

150162
From the blog Joyent, ranked 1 for the keywords “Node.js error handling”

0 commit comments

Comments
 (0)