This is the firstfleetinc error handler library, responsible for logging exceptions to papertrail and slack for our nodejs applications.
Also contains error handling middleware to be used with express in our api servers.
-
Run
npm install @firstfleet/fferrorhandler
-
Import
CommonJS
const ffErrorHandler = require("@firstfleet/fferrorhandler");
ES Module
import ffErrorHandler from "@firstfleet/fferrorhandler";
These environment variables are required in the ecosystem.config.js
used by pm2 (our node process manager), or in the
.launch.json
/debugging setup when running locally for the error handler to work.
-
SlackPosterURL
- Our cloud hosted slack logger endpoint -
PAPERTRAIL_HOST
- Papertrail host uri -
PAPERTRAIL_PORT
- Papertrail host port -
PAPERTRAIL_PROGRAM
- Application name used for logging -
PAPERTRAIL_HOSTNAME
- Host machines name used for logging
Optional
-
NON_OPERATIONAL_SLACK_CHANNEL
- Override default slack channel when using express middlewar
This property holds constants that map to slack channels. You can use this in your ffErrorHandler.logAndNotify call, to avoid mistyping the slack channel, or having to look them up.
- DEV ->
alerts_dev
- CRITICAL ->
alerts_critical
- MOBILE ->
alerts_mobile
- NODEJOBS ->
alerts_nodejobs
- SQL ->
alerts_sql
- WARNING ->
alerts_warning
- WEB ->
alerts_web
- WORKFLOW ->
alerts_workflow
- MOBILECOM ->
alerts_mobilecomm
- EDI ->
alerts_edi
Logs the error to papertrail, and an optional slack channel. If you do not pass in a slack channel, it will not log to slack.
Parameter | Type | Optional | Description |
---|---|---|---|
appName | string | false | Application Name, please use process.env.PAPERTRAIL_PROGRAM |
methodName | string | false | Name of the method the error is being logged from |
message | string | false | The error message, similar errors need to use the same message, in order to be muted together |
messageDetail | string | false | Additional error details, such as meta data, stack, etc |
slackChannel | string | true | Optional slack channel, if you want your error to go to slack. Please use ffErrorHandler.slackChannels constants for this |
const {logAndNotify, slackChannels} = require("@firstfleet/fferrorhandler");
const appName = process.env.PAPERTRAIL_PROGRAM;
function doThing() {
try {
throw new Error("oops");
} catch (error) {
// This done does NOT got to slack
logAndNotify(appName, "doThing", "My Error Message", "Extra error details, or maybe error.stack");
// This one DOES go to slack
logAndNotify(appName, "doThing", "My Error Message", "Extra error details, or maybe error.stack", slackChannels.WARNING);
}
}
The express middleware error handler methods are designed to be used with node and express.They follow the express error handling setup, and can be added to your express app, routers, or routes and be triggered by calling next(error)
. They are designed to be used with custom error types in ffErrorHandler.errorTypes.
Custom error types automatically combine any additional data provided with the error stack trace. So, when building additional data to be logged in the error type constructor, you do not need to provider the error stack trace.
To learn more about how express handles errors please read this
These custom error types work in sync with out custom express middleware error handlers.
const { errorTypes } = require("@firstfleet/fferrorhandler");
Sidebar - You most likely want to use HttpError rather than OperationalError. HttpError is a subset of OperationalError. You can use OperationalError directly, but it will send back a 500 to the client by default and no error message. HttpError will let you dictate the status code and the error message sent to the client.
An operational, or expected error. These are errors that developers do not need to know about and are normally low priority. Use this error when you want to log an error to papertrail, but not to slack. This will also send back a 500 to the client.
- Sends to slack:
false
- Sends to papertrail:
true
- Status code returned:
500
- Message returned to client when NODE_ENV === production:
Internal Server Error
- Message returned to client when NODE_ENV === development:
error.stack
Parameter | Type | Optional | Description |
---|---|---|---|
message | string | false | The error message, will not be sent to the client, but will be sent to papertrail |
additionalData | string | true | Any additional data you want logged to papertrail, will be combined with the stacktrace of the error. Defaults to empty string. |
options | {includeBody: boolean | true | Additional options for the error constructor. |
Options Parameter
Key | Type | Default Value | Description |
---|---|---|---|
includeBody | boolean | false | Whether to include req.body on the error payload |
const { errorTypes } = require("@firstfleet/fferrorhandler");
function handleRoute(req, res, next) {
try {
throw new errorTypes.OperationalError("My error message", "Additional error data");
} catch (error) {
// OperationalError will be passed onto express error handling.
// See section on express middleware methods
next(error);
}
}
Sidebar - You can also use a plain JS Error instead of a NonOperationalError, and it will mostly have the same result. However, regular Errors do not allow additional data to be passed in for logging.
Non Optional Errors are unexpected errors, or errors the developers want to be notified of in a timely manner. These errors get sent to papertrail and the specified slack channel. This error will result in a status code of 500 sent back to client, as something has failed unexpectedly.
Non Operational Errors by default get sent to alerts_warning
. If you want to change the default channel at the application level, you can set process.env.NON_OPERATIONAL_SLACK_CHANNEL
and it will use that as the default channel. If you want to change channel for a particular call (overriding either the alerts_warning default, or the process.env default), you can pass in a slack channel in the error constructor. These slack channels are also validated, and if an invalid channel id is passed in, it will fall back to alerts_warning
. To make sure you don't pass in an invalid slack channel, please use the constants in slackChannels.
- Sends to slack:
true
- Sends to papertrail:
true
- Status code returned:
500
- Message returned to client when NODE_ENV === production:
Internal Server Error
- Message returned to client when NODE_ENV === development:
error.stack
Parameter | Type | Optional | Description |
---|---|---|---|
message | string | false | The error message, will not be sent to the client, but will be sent to papertrail |
additionalData | string | true | Any additional data you want logged to papertrail, will be combined with the stacktrace of the error |
slackChannel | string | true | The slack channel to send the error to. |
options | {includeBody: boolean} | true | Additional options for the error constructor. |
Options Parameter
Key | Type | Default Value | Description |
---|---|---|---|
includeBody | boolean | false | Whether to include req.body on the error payload |
const { errorTypes, slackChannels } = require("@firstfleet/fferrorhandler");
function handleRoute(req, res, next) {
try {
throw new errorTypes.NonOperationalError("My error message", "Additional error data");
// OR with slackChannel override
throw new errorTypes.NonOperationalError("My error message", "Additional error data", slackChannels.WEB);
} catch (error) {
// NonOperationalError will be passed onto express error handling.
// See section on express middleware methods
next(error);
}
}
Sidebar - This is most likely the error you want to use if you need a status code other than 500, and a message to be returned to the client. However, these errors do NOT get logged to slack, only to papertrail. They are good for expected http errors (400, 401, 404). Should most likely not be used for status code 500, as a 500 would indicate an unexpected error and NonOperationalError should be used instead. If you are using status code 500, but don't want the error to go to slack, you should probably use a different status code.
An Http Error, it is a subset of Operational Error, and inherits some of its properties. This error will go to papertrail, and return the status code provided in the constructor, as well as the message and return them to the client.
- Sends to slack:
false
- This can be configured using the additional options payload, see the Options Parameters section below
- Sends to papertrail:
true
- Status code returned: Developer provided in error constructor
- Message returned: Developer provided in error constructor
Parameter | Type | Optional | Description |
---|---|---|---|
message | string | false | The error message, will not be sent to the client, but will be sent to papertrail |
statusCode | number | false | The status code you wish to return to the client |
additionalData | string | true | Any additional data you want logged to papertrail, will be combined with the stacktrace of the error |
options | {includeBody: boolean, slack: {send: boolean, channel: string}} | true | Additional options for the error constructor. |
Options Parameter
Key | Type | Default Value | Description |
---|---|---|---|
includeBody | boolean | false | Whether to include req.body on the error payload |
slack.send | boolean | false | Whether to send the error to slack, in addition to papertrail |
slack.channel | string | alerts_web | What channel to send the error to in slack |
const { errorTypes } = require("@firstfleet/fferrorhandler");
function handleRoute(req, res, next) {
try {
throw new errorTypes.HttpError("My error message", 400, "Additional error data");
} catch (error) {
// HttpError will be passed onto express error handling.
// See section on express middleware methods
next(error);
}
}
const { slackChannels } = require("@firstfleet/fferrorhandler");
const errorDetails = JSON.stringify({
user: "jess",
proc: "cool beans"
});
throw new errorTypes.HttpError(
"My error message",
500,
errorDetails,
{
includeBody: true,
slack: {
send: true,
channel: slackChannels.WARNING
}
});
- Sends to slack:
true
- Sends to papertrail:
true
- Status code returned: 500
- Message returned:
Internal Server Error
You can also send plain JS Errors to the error handling middleware, however, you are limited in your options as to what additional data can be included with the error.
Currently, you can only set the includeBody
option directly on the error, to include the req.body in the error payload.
const myHandler = async function(req, res, next) {
try {
throw new Error("My error message");
} catch (error) {
error.includeBody = true;
next(error);
}
}
These middleware methods are used to handle errors in nodejs express applications. There are two middleware methods, one is for logging errors, the other is responsible for responding with the error to the client. They are meant to always be used together, with the logger coming first in the stack, and the error handler coming last.
Sidebar - You may be asking, if they have to be used together, why not just make them one middleware method. The reason is so that consumers of these methods can add their own error handling middleware if desired, and choose where in the middleware stack they want to place it, as well as for separation of concerns.
Ideally, you would add the middleware methods to the express app, as app level middleware, but you can add them just to a router, or route if you need more fine grained control
This is an express middleware that is responsible for logging errors to slack and papertrail (or potentially anywhere, but currently set up for slack and papertrail). All errors this method logs that go to slack will go to the alerts_web
channel, will use the express route.path
as the method name, and will combine any additional data provided in the constructor with the error stack trace.It uses the errorTypes to determine where and how the error should be logged (slack, papertrail or both).
You can read more about this in the error types section but just as a reminder
OperationalError gets logged to papertrail
NonOperationalError gets logged to slack and papertrail. By default Non Operational errors are sent to alerts_warning
. You can change the default slack channel at the application level by setting process.env.NON_OPERATIONAL_SLACK_CHANNEL
. If you want to override the default channel (either the default alerts_warning
, or the one set in process.env.NON_OPERATIONAL_SLACK_CHANNEL
) in a particular call, you can pass in a slack channel into the error constructor.
Slack channels are validated against the values in slackChannels if you pass in a channel that is not in the slackChannels constant, it will fall back to alerts_warning
HttpError gets logged to papertrail
expressLogErrors Is meant to be used in conjunction with expressHandleErrors. expressLogErrors, after logging the error based on the error type, will forward the error to the next express error handler by calling next(error)
. These two middlewares are separate to allow allow consumers to add their own error handling middleware between them if needed and to separate concerns.
Sidebar - you can send plain JS Errors to the expressLogErrors middleware, and it will handle them, but it is really set up to work well with the custom error types, so when throw an error to be sent to next(error)
just consider what you want the logging behavior of that error to be and there should be a corresponding custom error type. The built in JS Error won't be able to attach additional data to the payload that gets logged, and will always be sent to slack.
See Middleware Examples for how to use this method with expressHandleErrors
This is an express middleware that is responsible for responding to the client when an error occurs. It will send back a status code, and sometimes a message depending on the error type. This will middleware will also hand over the error handling to express in edge cases, where the response has already been sent to the client. This should always be used as the final error handling middleware in the middleware stack.
You can read more about this in the error types section but just as a reminder
OperationalError responds with no message and a 500 to the client.
NonOperationalError responds with no message with a 500 to the client.
HttpError gets logged to papertrail, and sends back the message and status code provided in the constructor to the client.
See Middleware Examples for how to use this method with expressHandleErrors
Import the logging middleware
const { expressLogErrors, expressHandleErrors } = require("@firstfleet/fferrorhandler");
Add it to an express app - Normally what you want
const express = require('express');
const app = express();
const { expressLogErrors, expressHandleErrors, errorTypes } = require("@firstfleet/fferrorhandler");
app.post("/route", (req, res, next) => {
try {
/**
* The "Invalid Request" message will be sent to the client, and papertrail (not to slack, as this is an HttpError)
* The status code 400 will be sent to the client
* The "Additional" data will be logged to papertrail
*/
throw new errorTypes.HttpError("Invalid Request", 400, "Additional data");
} catch (error) {
next(error)
}
});
// To send errors to the middleware, all you need to do is call next(error), either in your route handler, or in another middleware
app.use(expressLogErrors);
// Should always be used with the expressHandleErrors middleware, with expressHandleErrors being at the bottom of the middleware stack
app.use(expressHandleErrors);
Add it to an express router
const express = require('express');
const app = express();
const router = express.Router();
const { expressLogErrors, expressHandleErrors, errorTypes } = require("@firstfleet/fferrorhandler");
router.post("/route", (req, res, next) => {
try {
/**
* The "Invalid Request" message will be sent to the client, and papertrail (not to slack, as this is an HttpError)
* The status code 400 will be sent to the client
* The "Additional" data will be logged to papertrail
*/
throw new errorTypes.HttpError("Invalid Request", 400, "Additional data");
} catch (error) {
next(error)
}
})
// To send errors to the middleware, all you need to do is call next(error), either in your route handler, or in another middleware
router.use(expressLogErrors);
// Should always be used with the expressHandleErrors middleware, with expressHandleErrors being at the bottom of the middleware stack
router.use(expressHandleErrors);
Add it to an express route
const express = require('express');
const app = express();
const router = express.Router();
const { expressLogErrors, expressHandleErrors, errorTypes } = require("@firstfleet/fferrorhandler");
// To send errors to the middleware, all you need to do is call next(error), either in your route handler, or in another middleware
router.post("/route", (req, res, next) => {
try {
/**
* The "Invalid Request" message will be sent to the client, and papertrail (not to slack, as this is an HttpError)
* The status code 400 will be sent to the client
* The "Additional" data will be logged to papertrail
*/
throw new errorTypes.HttpError("Invalid Request", 400, "Additional data");
} catch (error) {
next(error)
}
}, expressLogErrors, expressHandleErrors);
The error handler is set up to catch two types of uncaught error exceptions. Those are
uncaughtException
The error handler will first log the error to slack and papertrail, along with the stack trace, then it will exit the process, and let pm2 restart the server.
unhandledRejection
The error handler will log the error to slack and papertrail gracefully, rather than forcing the server to restart.
PapertrailErrorEvents
The error handler also listens for papertrail error events, and will log any errors to slack to let us know if the error handler is not able to log to papertrail, or is failing to send logs to papertrail.