New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@firstfleet/fferrorhandler

Package Overview
Dependencies
Maintainers
5
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@firstfleet/fferrorhandler - npm Package Compare versions

Comparing version
1.0.47
to
2.0.0
+29
constants.js
/**
* An object that holds error levels.
* @type {{COMMON: string, FATAL: string, CRITICAL: string}}
* @deprecated
*/
const errorLevels = {
COMMON: 'common',
CRITICAL: 'critical',
FATAL: 'fatal'
};
/**
* Slack channel constants
* */
const slackChannels = {
DEV: "alerts_dev",
CRITICAL: "alerts_critical",
MOBILE: "alerts_mobileapp",
NODEJOBS: "alerts_nodejobs",
SQL: "alerts_sql",
WARNING: "alerts_warning",
WEB: "alerts_web",
WORKFLOW: "alerts_workflow",
}
module.exports = {
errorLevels,
slackChannels
}
/**
* A custom error for operational (or expected) error.
* Operational errors will get logged to papertrial, but not to
* slack.
* */
class OperationalError extends Error {
/**
* Create an operational error.
* @param {string} message - The error message
* @param {string} [data] - Additional contextual data
* */
constructor(message, data = "") {
super(message);
Error.captureStackTrace(this, OperationalError);
this.name = "OperationalError";
this.isOperational = true;
this.data = { data: data, stack: this.stack };
}
}
/**
* A custom error for Non operational errors (or unexpected) errors.
* NonOperationalError will get sent to slack, and papertrial, and are errors
* that need to be addressed by the team.
* */
class NonOperationalError extends Error {
/**
* Create a non operational error
* @param {string} message - The error message
* @param {string} [data] - Additional contextual data
* */
constructor(message, data = "") {
super(message);
Error.captureStackTrace(this, NonOperationalError);
this.name = "NonOperationalError";
this.isOperational = false;
this.data = { data: data, stack: this.stack };
}
}
/**
* Custom sub error type of OperationalError that
* also takes in a status code. Use this error if you want
* the message logged to papertrial, sent to the client, and a status code other
* than 500 to be returned.
*
* Be mindful when using this error type, as it will have its error message returned
* to the client. We want to be careful what information we expose.
* */
class HttpError extends OperationalError {
/**
* Create an http error, with message and status code.
* @param {string} message - The error message
* @param {number} statusCode - The http status code
* @param {string} [data] - Additional contextual data
* */
constructor(message, statusCode, data = "") {
super(message);
Error.captureStackTrace(this, HttpError);
this.name = "HttpError";
this.statusCode = statusCode;
this.data = { data: data, stack: this.stack };
}
}
module.exports = {
OperationalError,
NonOperationalError,
HttpError
}
const logger = require('@firstfleet/fflogger');
const slack = require('@firstfleet/ffslack');
/**
* DEPRECATED! Still works by please use logAndNotify
* Currently setup to proxy calls to logAndNotify.
*
* IF YOU SEE THIS METHOD BEING USED, PLEASE REPALCE IT
* @param {string} sender - Machine sending the error
* @param {string} method - method sending the error
* @param {string} message - error description
* @param {string} severity - which errorLevel (defined in error handler)
* @param {boolean} isOperational - is it an operational error or programmer error
* @param {string} channelId - slack channel to post to if error is critical
* @param {boolean} sendToSlack - should the message be sent to slack, even if not critical
* @deprecated
* @returns {void}
*/
const handleError = (sender, method, message, severity, isOperational, channelId = 'alerts_critical', sendToSlack = false) => {
if (sendToSlack) {
module.exports.logAndNotify(sender, method, message, "", channelId);
} else {
module.exports.logAndNotify(sender, method, message, "");
}
};
/**
* Logs error message to papertrail and optionally posts to Slack
* @param {string} appName - Name of the application (ex: NodeJobsConsumer)
* @param {string} methodName - Name of method (ex: AuditCard)
* @param {string} message - Error message (just the error, no distinct details)
* @param {string} messageDetail - Details or stack trace of the error (ex: DriverID - BEAB)
* @param {string} [slackChannel] - If Slack post desired, this channel will be used
*/
const logAndNotify = (appName, methodName, message, messageDetail, slackChannel) => {
try {
/**
* Make sure we are using the env PAPERTRAIL_PROGRAM as the app name
* if it is available
* */
if (process?.env?.PAPERTRAIL_PROGRAM) {
appName = process.env.PAPERTRAIL_PROGRAM;
}
logger.error(appName, methodName, `Error: ${message}\n${messageDetail}`, '');
if (slackChannel) {
slack.PostToSlack(appName, `${methodName}-${message}`, slackChannel, messageDetail);
}
} catch (error) {
console.error(error);
}
};
/**
* Proxied to logAndNotify, DO NOT USE
*
* If you see this call being used in the code you are working on, please update to call
* logAndNotify instead.
* @deprecated
* */
function PostToSlack(appName, errorMessage, slackChannel, errorDetail) {
module.exports.logAndNotify(appName, "", errorMessage, errorDetail, slackChannel);
}
module.exports = {
handleError,
logAndNotify,
PostToSlack
}
const handlers = require("./handlers");
const errorTypes = require("./errorTypes");
const { slackChannels } = require("./constants");
const appName = process.env.PAPERTRAIL_PROGRAM || "UNKNOWN";
/**
* Middleware that handlers logging errors to papertrail, and sending non operational errors to slack.
* @async
* @param {Error|import("./errorTypes").OperationalError|import("./errorTypes").NonOperationalError|import("errorTypes").HttpError} error - The error that occurred
* @param {import("express").Request} req - The express request object
* @param {import("express").Response} res - The express response object
* @param {import("express".NextFunction)} next - The express next function
* @returns {Promise<void>}
* */
async function expressLogErrors(error, req, res, next) {
/**
* Custom error types in {@see errorTypes} combine the stack with optional additional data and put it on error.data
* if we have error.data, use that as the error details, if we don't, use the error stack by itself.
* @type {string}
* */
const additionalData = error.data || error.stack;
if (!error.isOperational) {
/** If this is an unexpected error, set the slack channel and send to slack and papertrail */
handlers.logAndNotify(appName, req.path, error.message, additionalData, slackChannels.WEB);
} else {
/** If this is an expected error, log to papertrail, with any additional data */
handlers.logAndNotify(appName, req.path, error.message, additionalData);
}
return next(error);
}
/**
* Middleware that handles errors for all routes in the captcha controller.
* This routers error handlers do not send back any error messages to the client
* EXCEPT for when an HttpError is used, this is for increased security and not exposing
* internal errors.
*
* @async
* @param {Error|import("./errorTypes").OperationalError|import('./errorTypes').HttpError} error - The error that occurred
* @param {import("express").Request} req - The express request object
* @param {import("express").Response} res - The express response object
* @param {import("express".NextFunction)} next - The express next function
* @returns {Promise<void>}
*/
async function expressHandleErrors(error, req, res, next) {
/**
* If we have already sent a reply to the client
* let the express default error handling take over
* @see {@link https://expressjs.com/en/guide/error-handling.html}
* */
if (res.headersSent) {
return next(error);
}
/**
* If the error is an {@see errorTypes.HttpError} send the status code, and the message back
* to the client. We don't send back the addition data in error.data though, that is only
* meant for internal logging in {@see logErrors}
* */
if (error instanceof errorTypes.HttpError) {
res.status(error.statusCode).send(error.message);
} else {
/**
* In dev mode, send back the stack trace
* otherwise, send back a generic error message.
* */
if (process.env.NODE_ENV === "development") {
return res.status(500).send(error.stack);
} else {
return res.status(500).send("Internal Server Error");
}
}
}
module.exports = {
expressLogErrors,
expressHandleErrors
};
const { errorLevels, slackChannels } = require("../constants");
describe("constants.js", () => {
describe("errorLevels", () => {
it("Should map COMMON to 'common'.", () => {
expect(errorLevels.COMMON).toBe("common");
});
it("Should map CRITICAL to 'critical'.", () => {
expect(errorLevels.CRITICAL).toBe("critical");
});
it("Should map FATAL to 'fatal'.", () => {
expect(errorLevels.FATAL).toBe("fatal");
});
});
describe("slackChannels", () => {
it("Should map DEV to alerts_dev", () => {
expect(slackChannels.DEV).toBe("alerts_dev");
});
it("Should map CRITICAL to alerts_critical", () => {
expect(slackChannels.CRITICAL).toBe("alerts_critical");
});
it("Should map MOBILE to alerts_mobileapp", () => {
expect(slackChannels.MOBILE).toBe("alerts_mobileapp");
});
it("Should map NODEJOBS to alerts_nodejobs", () => {
expect(slackChannels.NODEJOBS).toBe("alerts_nodejobs");
});
it("Should map SQl to alerts_sql", () => {
expect(slackChannels.SQL).toBe("alerts_sql");
});
it("Should map WARNING to alerts_warning", () => {
expect(slackChannels.WARNING).toBe("alerts_warning");
});
it("Should map WEB to alerts_web", () => {
expect(slackChannels.WEB).toBe("alerts_web");
});
it("Should map WORKFLOW to alerts_workflow", () => {
expect(slackChannels.WORKFLOW).toBe("alerts_workflow");
});
});
})
const errorTypes = require("../errorTypes");
describe("errorTypes.js", () => {
describe("OperationalError", () => {
const testErrorMessage = "test message";
const additionalErrorData = "extra data";
const operationalError = new errorTypes.OperationalError(testErrorMessage, additionalErrorData);
it("should be instance of OperationalError.", () => {
expect(operationalError instanceof errorTypes.OperationalError).toBe(true);
});
it("should have name OperationalError.", () => {
expect(operationalError.name).toBe("OperationalError");
});
it("should have property isOperational, and it should be true.", () => {
expect(operationalError.isOperational).toBe(true);
});
it(`should have the message ${testErrorMessage}.`, () => {
expect(operationalError.message).toBe(testErrorMessage);
});
it(`should have the data ${additionalErrorData}.`, () => {
expect(operationalError.data).toEqual({ data: additionalErrorData, stack: operationalError.stack });
});
});
describe("NonOperationalError", () => {
const testErrorMessage = "test non operationalError";
const additionalErrorData = "test additional data";
const nonOperationalError = new errorTypes.NonOperationalError(testErrorMessage, additionalErrorData);
it("should be instance of NonOperationalError", () => {
expect(nonOperationalError instanceof errorTypes.NonOperationalError).toBe(true);
});
it("should have name NonOperationalError", () => {
expect(nonOperationalError.name).toBe("NonOperationalError");
});
it("should have property isOperational, and it should be false.", () => {
expect(nonOperationalError.isOperational).toBe(false);
});
it(`should have the message ${testErrorMessage}.`, () => {
expect(nonOperationalError.message).toBe(testErrorMessage);
});
it(`should have the data ${additionalErrorData}.`, () => {
expect(nonOperationalError.data).toEqual({ data: additionalErrorData, stack: nonOperationalError.stack });
});
})
describe("HttpError", () => {
const testErrorMessage = "test http error";
const testStatusCode = 401;
const additionalErrorData = "test additional data";
const httpError = new errorTypes.HttpError(testErrorMessage, testStatusCode, additionalErrorData);
it("should be instance of HttpError.", () => {
expect(httpError instanceof errorTypes.HttpError).toBe(true);
});
it("should have name HttpError.", () => {
expect(httpError.name).toBe("HttpError");
});
it("should have property isOperational, and it should be true.", () => {
expect(httpError.isOperational).toBe(true);
});
it(`should have the message ${testErrorMessage}.`, () => {
expect(httpError.message).toBe(testErrorMessage);
});
it(`should have the data ${additionalErrorData}.`, () => {
expect(httpError.data).toEqual({ data: additionalErrorData, stack: httpError.stack });
});
it(`should have the status code ${testStatusCode}.`, () => {
expect(httpError.statusCode).toBe(testStatusCode);
});
})
})
jest.mock("@firstfleet/ffslack", () => ({
PostToSlack: jest.fn()
}));
jest.mock("@firstfleet/fflogger", () => ({
error: jest.fn()
}));
const handlers = require("../handlers");
const { slackChannels } = require("../constants");
const slack = require("@firstfleet/ffslack");
const logger = require("@firstfleet/fflogger");
const appName = process.env.PAPERTRAIL_PROGRAM;
const testSender = appName;
const testMethod = "testMethod";
const testDetails = "testDetails";
const testMessage = "testMessage";
const testSeverity = "testSeverity";
const testIsOperational = true;
const testChannelId = slackChannels.WEB;
let testSendToSlack = true;
const handlersCopy = Object.assign({}, handlers);
afterEach(() => {
process.env.PAPERTRAIL_PROGRAM = "appName";
jest.resetAllMocks();
})
describe("handlers.js", () => {
describe("handlerError", () => {
it("Should call logAndNotify with a channelId if sendToSlack === true", () => {
handlers.logAndNotify = jest.fn();
handlers.handleError(testSender, testMethod, testMessage, testSeverity, testIsOperational, testChannelId, testSendToSlack);
expect(handlers.logAndNotify).toHaveBeenCalledWith(testSender, testMethod, testMessage, "", testChannelId);
});
it("Should call logAndNotify without a channelId if sendToSlack = false", () => {
handlers.logAndNotify = jest.fn();
testSendToSlack = false;
handlers.handleError(testSender, testMethod, testMessage, testSeverity, testIsOperational, testChannelId, testSendToSlack);
expect(handlers.logAndNotify).toHaveBeenCalledWith(testSender, testMethod, testMessage, "");
});
});
describe("logAndNotify", () => {
it("Should change appName to process.env.PAPERTRAIL_PROGRAM program if it exist", () => {
const differentAppName = "differentAppName";
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(differentAppName, testMethod, testMessage, testDetails, testChannelId);
expect(logger.error).toHaveBeenCalledWith(appName, testMethod, `Error: ${testMessage}\n${testDetails}`, '');
expect(slack.PostToSlack).toHaveBeenCalledWith(appName, `${testMethod}-${testMessage}`, testChannelId, testDetails);
});
it("Should use the passed in appName if PAPERTRAIL_PROGRAM is not in the env variables", () => {
delete process.env.PAPERTRAIL_PROGRAM;
const differentAppName = "differentAppName";
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(differentAppName, testMethod, testMessage, testDetails, testChannelId);
expect(logger.error).toHaveBeenCalledWith(differentAppName, testMethod, `Error: ${testMessage}\n${testDetails}`, '');
expect(slack.PostToSlack).toHaveBeenCalledWith(differentAppName, `${testMethod}-${testMessage}`, testChannelId, testDetails);
});
it("Should call logger.error", () => {
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(appName, testMethod, testMessage, testDetails, testChannelId);
expect(logger.error).toHaveBeenCalledWith(appName, testMethod, `Error: ${testMessage}\n${testDetails}`, '');
});
it("Should call PostToSlack if a slackChannel is provided", () => {
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(appName, testMethod, testMessage, testDetails, testChannelId);
expect(slack.PostToSlack).toHaveBeenCalledWith(appName, `${testMethod}-${testMessage}`, testChannelId, testDetails);
});
it("Should not call PostToSlack if no slackChannel is provided", () => {
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(appName, testMethod, testMessage, testDetails);
expect(slack.PostToSlack).not.toHaveBeenCalled();
});
it("Should call console.error if it fails to log", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const slackError = new Error("test");
slack.PostToSlack.mockImplementation(() => { throw slackError });
handlers.logAndNotify = handlersCopy.logAndNotify;
handlers.logAndNotify(appName, testMethod, testMessage, testDetails, testChannelId);
expect(consoleErrorSpy).toHaveBeenCalledWith(slackError);
consoleErrorSpy.mockClear()
slack.PostToSlack.mockReset();
});
});
describe("PostToSlack", () => {
it("Should call logAndNotify, and pass through parameters", () => {
handlers.logAndNotify = jest.fn();
handlers.PostToSlack(appName, testMessage, testChannelId, testDetails);
expect(handlers.logAndNotify).toHaveBeenCalledWith(appName, "", testMessage, testDetails, testChannelId);
});
})
});
jest.mock("@firstfleet/fflogger", () => {
const EventEmitter = require("events")
const emitter = new EventEmitter();
return {
PaperTrailErrorEvent: emitter
}
});
jest.mock("../handlers", () => ({
logAndNotify: jest.fn(),
handleError: jest.fn(),
}));
jest.mock("../middleware", () => ({
expressLogErrors: jest.fn(),
expressHandleErrors: jest.fn()
}));
const logger = require("@firstfleet/fflogger");
const { slackChannels, errorLevels } = require("../constants");
const handlers = require("../handlers");
const { expressLogErrors, expressHandleErrors } = require("../middleware");
const main = require("../index");
const appName = process.env.PAPERTRAIL_PROGRAM;
jest.useFakeTimers();
beforeEach(() => {
jest.resetModules();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("index.js", () => {
const testErrorMessage = "test message";
const testError = new Error(testErrorMessage);
it("Should handle papertrail errors by calling console.error and logAndNotify emmited from logger.PaperTrailErrorEvent", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
setTimeout(() => {
logger.PaperTrailErrorEvent.emit("error", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
expect(handlers.logAndNotify).toHaveBeenCalledWith(
appName,
"Error Handler Papertrail Init",
`Unable to log to Papertrail: ${testError.message}`,
testError.stack,
slackChannels.WARNING
);
consoleErrorSpy.mockClear();
});
it("Should call console.error if an error is thrown in PaperTrailErrorEvent.on('error')", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const logError = new Error("log error");
handlers.logAndNotify.mockImplementation(() => { throw logError })
setTimeout(() => {
logger.PaperTrailErrorEvent.emit("error", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, logError);
consoleErrorSpy.mockClear();
handlers.logAndNotify.mockReset();
});
it("Should call console.error and logAndNotify, then gracefully shutdown the server if there is an uncaught exception", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation();
setTimeout(() => {
process.emit("uncaughtException", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
expect(handlers.logAndNotify).toHaveBeenCalledWith(
appName,
'GLOBAL - uncaughtException',
testError.message,
testError.stack,
slackChannels.CRITICAL
);
jest.advanceTimersByTime(1000);
expect(processExitSpy).toHaveBeenCalledWith(1);
consoleErrorSpy.mockClear()
processExitSpy.mockClear()
});
it("Should call console.error and then gracefully shutdown the server if there is an uncaught exception and logAndNotify throws an error", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation();
const logError = new Error("log error");
handlers.logAndNotify.mockImplementation(() => { throw logError })
setTimeout(() => {
process.emit("uncaughtException", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, logError);
expect(processExitSpy).toHaveBeenCalledWith(1);
consoleErrorSpy.mockClear()
processExitSpy.mockClear()
handlers.logAndNotify.mockReset();
});
it("Should call console.error and logAndNotify if there is an unhandledRejection error thrown", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
setTimeout(() => {
process.emit("unhandledRejection", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenCalledWith(testError);
expect(handlers.logAndNotify).toHaveBeenCalledWith(
appName,
'GLOBAL - unhandledRejection',
testError.message,
testError.stack,
slackChannels.CRITICAL
);
consoleErrorSpy.mockClear()
});
it("Should call console.error if there is an unhandledRejection error thrown, and logAndNotify throws an error", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const logError = new Error("log error");
handlers.logAndNotify.mockImplementation(() => { throw logError })
setTimeout(() => {
process.emit("unhandledRejection", testError)
}, 100);
jest.advanceTimersByTime(100)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, logError);
consoleErrorSpy.mockClear()
handlers.logAndNotify.mockReset();
});
});
const middleware = require("../middleware");
const errorTypes = require("../errorTypes");
const handlers = require("../handlers");
const { slackChannels } = require("../constants");
jest.mock("@firstfleet/ffslack", () => ({
PostToSlack: jest.fn().mockReturnValue({})
}));
describe("middleware.js", () => {
const testErrorMessage = "test";
const testAdditionalData = "test data";
const testStatusCode = 400;
const normalError = new Error(testErrorMessage);
const operationalError = new errorTypes.OperationalError(testErrorMessage, testAdditionalData);
const nonOperationalError = new errorTypes.NonOperationalError(testErrorMessage, testAdditionalData);
const httpError = new errorTypes.HttpError(testErrorMessage, testStatusCode, testAdditionalData);
const mockReq = {
path: "/test/path"
};
const createMockRes = () => {
const res = {};
res.headersSent = false;
res.sendStatus = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
res.status = jest.fn().mockReturnValue(res);
return res;
}
const mockRes = createMockRes();
const mockNext = jest.fn();
const slackChannel = slackChannels.WEB;
handlers.logAndNotify = jest.fn().mockImplementation(() => { });
afterEach(() => {
mockNext.mockClear();
})
describe("expressLogErrors", () => {
const appName = process.env.PAPERTRAIL_PROGRAM;
it("should log the error to slack channel alerts_web, and then call the next() function if the error is a JS Error.", async () => {
await middleware.expressLogErrors(normalError, mockReq, mockRes, mockNext);
expect(handlers.logAndNotify)
.toHaveBeenCalledWith(appName,
mockReq.path,
normalError.message,
normalError.stack,
slackChannel
);
expect(mockNext).toHaveBeenCalledWith(normalError);
});
it("should log the error to slack channel alerts_web, and then call the next() function if the error is a non operational error.", async () => {
await middleware.expressLogErrors(nonOperationalError, mockReq, mockRes, mockNext);
expect(handlers.logAndNotify)
.toHaveBeenCalledWith(appName,
mockReq.path,
normalError.message,
normalError.stack,
slackChannel
);
expect(mockNext).toHaveBeenCalledWith(normalError);
});
it("should log the error only to papertrail, if the error is an operational error and then call the next() function.", async () => {
await middleware.expressLogErrors(operationalError, mockReq, mockRes, mockNext);
expect(handlers.logAndNotify)
.toHaveBeenCalledWith(
appName,
mockReq.path,
operationalError.message,
operationalError.data
);
expect(mockNext).toHaveBeenCalledWith(operationalError);
});
it("should log the error only to papertrail, if the error is an http error, and then call the next() function.", async () => {
await middleware.expressLogErrors(httpError, mockReq, mockRes, mockNext);
expect(handlers.logAndNotify)
.toHaveBeenCalledWith(
appName,
mockReq.path,
httpError.message,
httpError.data
);
expect(mockNext).toHaveBeenCalledWith(httpError);
});
});
describe("expressHandleErrors", () => {
afterEach(() => {
mockRes.headersSent = false;
mockRes.status.mockClear();
mockRes.send.mockClear();
mockRes.sendStatus.mockClear();
});
it("Should call next(error) if the response has alread been sent.", async () => {
mockRes.headersSent = true;
await middleware.expressHandleErrors(normalError, mockReq, mockRes, mockNext);
expect(mockNext).toHaveBeenCalledWith(normalError);
expect(mockRes.status).not.toHaveBeenCalled();
expect(mockRes.status().send).not.toHaveBeenCalled();
expect(mockRes.sendStatus).not.toHaveBeenCalled();
});
it("should call res.status.send the error is an http error.", async () => {
await middleware.expressHandleErrors(httpError, mockReq, mockRes, mockNext);
expect(mockNext).not.toHaveBeenCalled();
expect(mockRes.status).toHaveBeenCalledWith(httpError.statusCode);
expect(mockRes.status().send).toHaveBeenCalledWith(httpError.message);
});
it("should call res.status.send with a 500, and the message 'Internal Server Error' if NODE_ENV === production if it is not an http error.", async () => {
process.env.NODE_ENV = "production";
await middleware.expressHandleErrors(operationalError, mockReq, mockRes, mockNext);
expect(mockNext).not.toHaveBeenCalled();
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.status().send).toHaveBeenCalledWith("Internal Server Error");
});
it("should call res.status.send with a 500, and the message of error.stack if it is not an http error and NODE_ENV === development.", async () => {
process.env.NODE_ENV = "development";
await middleware.expressHandleErrors(operationalError, mockReq, mockRes, mockNext);
expect(mockNext).not.toHaveBeenCalled();
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.status().send).toHaveBeenCalledWith(operationalError.stack);
});
});
})
process.env.PAPERTRAIL_PROGRAM = "appName";
+67
-173

@@ -1,180 +0,65 @@

'use strict';
const logger = require('@firstfleet/fflogger');
const { PostToSlack } = require('@firstfleet/ffslack');
const { errorLevels, slackChannels } = require("./constants");
const { handleError, logAndNotify, PostToSlack } = require("./handlers");
const { expressLogErrors, expressHandleErrors } = require("./middleware");
//Try to listen for Papertrail logging events and post to Slack
if (logger?.PaperTrailErrorEvent){
logger.PaperTrailErrorEvent.on('error', (error) => {
console.error(error.message);
PostToSlack(process.env.PAPERTRAIL_PROGRAM || 'UNKNOWN', `Unable to log to Papertrail: ${error.message}`, 'alerts_warning', error.detail);
})
}
const appName = process.env.PAPERTRAIL_PROGRAM || "UNKNOWN";
/**
* An object that holds error levels.
* @type {{COMMON: string, FATAL: string, CRITICAL: string}}
*/
const errorLevels = {
COMMON: 'common',
CRITICAL: 'critical',
FATAL: 'fatal'
};
/**
* How long to wait before gracefully shutting down the server
* when we get an `uncaughtException` error
* */
const WAITBEFORESHUTDOWN = 1000;
/**
* Creates a new AppError
* @class
*/
class AppError {
constructor(sender, method, message, severity, isOperational, channelId, sendToSlack) {
Error.call(this);
Error.captureStackTrace(this);
this.sender = sender;
this.method = method;
this.message = message;
this.severity = severity;
this.isOperational = isOperational;
this.channelId = channelId;
this.sendToSlack = sendToSlack;
}
}
/**
* Attach error prototypical inheritance
*/
AppError.prototype.__proto__ = Error.prototype;
const logError = (error) => {
return new Promise((resolve, reject) => {
/**
* Try to listen for Papertrail logging error events and post to Slack
* */
if (logger?.PaperTrailErrorEvent) {
logger.PaperTrailErrorEvent.on('error', (error) => {
try {
logger.error(error.sender, error.method, error.message, error);
if (error.severity === errorLevels.CRITICAL || error.sendToSlack) {
PostToSlack(error.sender, error.message, error.channelId, error.stack);
}
resolve('Error Logged')
} catch(err) {
reject(err)
console.error(error);
logAndNotify(
appName,
"Error Handler Papertrail Init",
`Unable to log to Papertrail: ${error.message}`,
error.stack,
slackChannels.WARNING
);
} catch (error) {
console.error(error);
}
});
};
}
const logUncaughtError = (error) => {
return new Promise((resolve, reject) => {
try {
logger.error('Uncaught Error', 'Global Error Catch', `Error: ${error.message}\n${error.stack}`);
try {
PostToSlack(process.env.PAPERTRAIL_PROGRAM || 'Unknown App', `Global error catch - ${error.message}`, 'alerts_warning', error.stack);
} catch (error) {
console.log(error.message);
}
resolve(error)
} catch(err) {
reject(error)
}
});
};
/**
* Handles application errors and calls the ffLogger (sends log to console if not
* in production and also to papertrail). Collects params and stack trace.
* @param sender - Machine sending the error
* @param method - method sending the error
* @param message - error description
* @param severity - which errorLevel (defined in error handler)
* @param isOperational - is it an operational error or programmer error
* @param channelId - slack channel to post to if error is critical
* @param sendToSlack - should the message be sent to slack, even if not critical
* @returns {Promise<any>}
* Global handler for all uncaught exceptions,
* gracefully restart the server after logging to slack
*/
const handleError = (sender, method, message, severity, isOperational, channelId = 'alerts_critical', sendToSlack = false) => {
return new Promise((resolve, reject) => {
const error = new AppError(sender, method, message, severity, isOperational, channelId, sendToSlack);
return logError(error)
.then(error => resolve(error))
.catch(caughtError => {
console.error(caughtError);
reject(caughtError)
})
});
};
/**
* Logs error message to papertrail and optionally posts to Slack
* @param {string} appName - Name of the application (ex: NodeJobsConsumer)
* @param {string} methodName - Name of method (ex: AuditCard)
* @param {string} message - Error message (just the error, no distinct details)
* @param {string} messageDetail - Details or stack trace of the error (ex: DriverID - BEAB)
* @param {string} [slackChannel] - If Slack post desired, this channel will be used
*/
const logAndNotify = (appName, methodName, message, messageDetail, slackChannel) => {
process.on('uncaughtException', (error) => {
try {
logger.error(appName, methodName, `Error: ${message}\n${messageDetail}`, '');
if (slackChannel){
PostToSlack(appName, `${methodName}-${message}`, slackChannel, messageDetail);
}
} catch (error) {
console.error(error.message);
}
};
console.error(error);
logAndNotify(
appName,
'GLOBAL - uncaughtException',
error.message,
error.stack,
slackChannels.CRITICAL
);
const createError = (sender = 'errorhandler', method = 'default method', message = 'default error message', severity = errorLevels.COMMON, isOperational = false, channelId = 'alerts_critical', sendToSlack = 'false') => {
return new AppError(sender, method, message, severity, isOperational, channelId, sendToSlack);
};
/**
* If logging has not finished in 1 second, go ahead and
* gracefully shutdown the server
* */
setTimeout(() => {
process.exit(1);
}, WAITBEFORESHUTDOWN)
const handleErrorList = (errorList) => {
let finalErrorList = []; //new AppError(sender, method, message, severity, isOperational, channelId, sendToSlack);
} catch (error) {
console.error(error);
//Loop over list of error candidates
errorList.map(error => {
let errorExists = false;
//Loop over existing errors to see if we've seen this one yet
finalErrorList.forEach(finalError => {
if (finalError.messsage === error.message){ //If found, increment the counter on that one
errorExists = true;
finalError.errorCount = finalError.errorCount + 1;
return error
}
});
//If it's a new one, add it to the final distinct array.
if (!errorExists){
finalErrorList.push(Object.assign(error, {'errorCount': 0}))
}
});
//Send distinct error list
finalErrorList.forEach(error => {
logError(error);
})
};
/**
* Takes in a custom error object and return a boolean. If the error
* is an operation error it returns true, and returns false if it is a
* Programmer error.
* @param error
* @returns {*}
*/
const isTrustedError = (error) => {
return error.isOperational;
};
/**
* Global handler for all uncaught exceptions
* If an uncaughtException is not an Operational error,
* gracefully restart the application.
*/
process.on('uncaughtException', (error) => {
logUncaughtError(error)
.then(_error => {
if (!isTrustedError(_error)) {
process.exit(1)
}
})
.catch(caughtError => {
console.error(error);
process.exit(1)
})
/**
* We don't need a timeout here, as console.error is synchronous
* */
process.exit(1);
}
});

@@ -187,16 +72,25 @@

*/
process.on('unhandledRejection', error => {
logUncaughtError(error);
process.on('unhandledRejection', (error) => {
try {
console.error(error);
logAndNotify(
appName,
'GLOBAL - unhandledRejection',
error.message,
error.stack,
slackChannels.CRITICAL
);
} catch (error) {
console.error(error);
}
});
module.exports = {
AppError,
slackChannels,
handleError,
isTrustedError,
errorLevels,
logAndNotify,
PostToSlack,
handleErrorList,
createError,
logAndNotify,
logger
expressLogErrors,
expressHandleErrors
};
{
"name": "@firstfleet/fferrorhandler",
"version": "1.0.47",
"description": "handle errors",
"main": "index.js",
"scripts": {
"send": "npm publish --access public",
"build-docs": "documentation build index.js -f md --shallow -o docs.md"
},
"author": "Jess Patton",
"license": "ISC",
"dependencies": {
"@firstfleet/fflogger": "^1.0.27",
"@firstfleet/ffslack": "^1.0.11"
}
"name": "@firstfleet/fferrorhandler",
"version": "2.0.0",
"description": "handle errors",
"main": "index.js",
"scripts": {
"send": "npm publish --access public",
"build-docs": "documentation build index.js -f md --shallow -o docs.md",
"test": "jest",
"test-coverage": "jest --coverage",
"test-watch": "jest --watch"
},
"author": "Jess Patton",
"license": "ISC",
"dependencies": {
"@firstfleet/fflogger": "^1.0.27",
"@firstfleet/ffslack": "^1.0.11"
},
"jest": {
"setupFiles": [
"./test/setup.js"
]
},
"volta": {
"node": "14.15.0"
},
"devDependencies": {
"jest": "^28.1.3"
}
}
+334
-58

@@ -1,85 +0,361 @@

# @firstfleet/fferrorhandler
# ffErrorHandler
A centralized error handler. Uses a custom error object so we not only get the stack trace but we can
also track attributes the tie into papertrails log aggregation. Those attributes we are using are,
sender, program, message, severity. We also set a custom attribute called isOperational. This denotes if
an error is an operational error or a programmer error.
This is the firstfleetinc error handler library, responsible for logging exceptions to papertrail and slack for our nodejs applications.
You can also pass in a slack channel name (defaults to alerts_critical) and a boolean, sendToSlack. If an alert is
set to critical, it will auto send to slack. If it is not critical, if will only send to slack if sendToSlack is true.
You will also need to set an env variable, process.env.SlackPosterURL.
Also contains error handling middleware to be used with express in our api servers.
## Index
The error handler also handles all uncaughtException, and unhandledRejection. On uncaughtException if the error
is Programmer, the process is restarted after the error is logged.
* [Install](#use)
* [Environment Variables](#environment-variables)
* [Constants](#constants)
* [Methods](#methods)
* [logAndNotify](#log-and-notify)
* [Middleware](#middleware)
* [Error Types](#error-types)
* [Operational Error](#operational-error)
* [NonOperational Error](#non-operational-error)
* [Http Error](#http-error)
* [Middleware Methods](#middleware-methods)
* [expressLogErrors](#express-log-errors)
* [expressHandleErrors](#express-handle-errors)
* [Middleware Examples](#middleware-examples)
* [Uncaught Errors](#uncaught-errors)
If you update the js docs you can rebuild them by running
```npm run build-docs```
## Use<a id="use"></a>
To publish to npm
1) Run
```
npm install @firstfleet/fferrorhandler
```
2) Import
1) Increment the package version
2) Login by running ```npm login```
3) Make sure you have been added to the firstfleet org
2) run ```npm run send```
CommonJS
```
const ffErrorHandler = require("@firstfleet/fferrorhandler");
```
# Error Handler
Exports a custom error type AppError. Can be flagged as a programmer error or an operational error.
All errors should be capture, when you want to throw an error, import the module
```js
const { AppError } = require('...pathToErrorModule/errorHandler');
```
ES Module
```
import ffErrorHandler from "@firstfleet/fferrorhandler";
```
---
Then throw the custom error. The custom error takes in a sender, method, message, severity and a boolean for isOperational
```js
throw new AppError('Node1', 'callApiMethod', 'api call failed', errorLevels.COMMON, true, 'alerts_critical', true);
## Required Environment Variables<a id="environment-variables"></a>
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.
1) `SlackPosterURL` - Our cloud hosted slack logger endpoint
2) `PAPERTRAIL_HOST` - Papertrail host uri
3) `PAPERTRAIL_PORT` - Papertrail host port
4) `PAPERTRAIL_PROGRAM` - Application name used for logging
5) `PAPERTRAIL_HOSTNAME` - Host machines name used for logging
---
## Constants<a id="constants"></a>
### ffErrorHandler.slackChannels<a id="slack-channel"></a>
This property holds constants that map to slack channels. You can use this in your [ffErrorHandler.logAndNotify](#log-and-notify) 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`
---
## Methods<a id="methods"></a>
### ffErrorHandler.logAndNotify<a id="log-and-notify"></a>
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](#slack-channel) constants for this|
```js
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);
}
}
```
There is also a built in error handler. You can import it
---
```js
const { handleError, errorLevels } = require('@firstfleet/fferrorhandler');
## Express Middleware<a id="middleware"></a>
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](#error-types).
**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](https://expressjs.com/en/guide/error-handling.html)**
### Error Types<a id="error-types"></a>
These custom error types work in sync with out custom express middleware error handlers.
#### Import The Error Types
```js
const { errorTypes } = require("@firstfleet/fferrorhandler");
```
Then call it when you want to handle an error. Error levels can be imported from the errorLevels object.
```js
handleError(sender, method, message, severity, isOperational, slackChannelname = 'alerts_critical', sendtToSlack = true, enableStackTrace = true);
handleError('Node1', 'handleInput', 'totally broke the input box by adding script tags', errorLevels.COMMON, true);
#### Types
##### Operational Error<a id="operational-error"></a>
*Sidebar - You most likely want to use [HttpError](#http-error) rather than [OperationalError](#operational-error). [HttpError](#http-error) is a subset of [OperationalError](#operational-error). You can use [OperationalError](#operational-error) directly, but it will send back a 500 to the client by default and no error message. [HttpError](#http-error) 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.|
```js
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);
}
}
```
The error handler will transform the error into our custom AppError type, send it to syslog (papertrail), and console log it if the process.ENV !== 'production'
##### Non Operational Error<a id="non-operational-error"></a>
# Log And Notify Method
A similar error handling method is also available named logAndNotify(). The key difference is it does not automatically get the error stack (sometimes it's more precise to pass in the error stack to get it one level up). This method also takes fewer parameters and posts to Slack based on if a channel was passed on.
*Sidebar - You can also use a plain JS Error instead of a [NonOperationalError](#non-operational-error), and it will mostly have the same result. However, regular Errors do not allow additional data to be passed in for logging.*
```js
const handleError = require('@firstfleet/fferrorhandler');
try{
let testing = 1
}
catch(error){
errorHandler.logAndNotify(process.env.APP_NAME, 'myMethod', error.message, error.stack, 'alerts_warning')
}
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.
* 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|
```js
const { errorTypes } = require("@firstfleet/fferrorhandler");
function handleRoute(req, res, next) {
try {
throw new errorTypes.NonOperationalError("My error message", "Additional error data");
} catch (error) {
// NonOperationalError will be passed onto express error handling.
// See section on express middleware methods
next(error);
}
}
```
#### Http Error<a id="http-error"></a>
### Handle a list of errors
```js
*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.*
const {createError, handleErrorList } = require('@firstfleet/fferrorhandler')
let errorList = array.map(item => {
if (item.error) {
return createError(sender, method, message, severity, isOperational, channelId, sendToSlack)
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`
* 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|
```js
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);
}
}
})
```
handleErrorList(errorList)
---
### Express Middleware Methods<a id="middleware-methods"></a>
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**
#### ffErrorHandler.expressLogErrors<a id="express-log-errors"></a>
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](#error-types) 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](#error-types) but just as a reminder
[OperationalError](#operational-error) gets logged to papertrail
[NonOperationalError](#non-operational-error) gets logged to slack and papertrail
[HttpError](#http-error) gets logged to papertrail
[expressLogErrors](#express-log-errors) Is meant to be used in conjunction with [expressHandleErrors](#express-handle-errors). [expressLogErrors](#express-log-errors), after logging the error based on the [error type](#error-types), 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](#express-log-errors) 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](#error-types). 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](#middleware-examples) for how to use this method with [expressHandleErrors](#express-handle-errors)**
#### ffErrorHandler.expressHandleErrors<a id="express-handle-errors"></a>
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](#error-types). 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](#error-types) but just as a reminder
[OperationalError](#operational-error) responds with no message and a 500 to the client.
[NonOperationalError](#non-operational-error) responds with no message with a 500 to the client.
[HttpError](#http-error) gets logged to papertrail, and sends back the message and status code provided in the constructor to the client.
**See [Middleware Examples](#middleware-examples) for how to use this method with [expressHandleErrors](#express-handle-errors)**
#### Middleware Examples<a id="middleware-examples"></a>
Import the logging middleware
```js
const { expressLogErrors, expressHandleErrors } = require("@firstfleet/fferrorhandler");
```
### Programmer Error
Programmer errors refer to cases where you have no idea why and sometimes where an error came from – it might be some code that tried to read an undefined value or DB connection pool that leaks memory.
### Operational Error
Operational errors refer to situations where you understand what happened and the impact of it – for example, a query to some HTTP service failed due to connection problem.
Add it to an express app - **Normally what you want**
```js
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
```js
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
```js
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);
```
---
## Handling Uncaught Errors In Node Apps<a id="uncaught-errors"></a>
The error handler is set up to catch two types of uncaught error exceptions. Those are
1) `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.
2) `unhandledRejection`
The error handler will log the error to slack and papertrail gracefully, rather than forcing the server to restart.
3) `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.
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [errorLevels][1]
- [AppError][2]
- [Parameters][3]
- [\_\_proto\_\_][4]
- [handleError][5]
- [Parameters][6]
- [isTrustedError][7]
- [Parameters][8]
- [on][9]
- [on][10]
- [index][11]
## errorLevels
An object that holds error levels.
Type: {COMMON: [string][12], FATAL: [string][12], CRITICAL: [string][12]}
## AppError
Creates a new AppError
### Parameters
- `sender`
- `method`
- `message`
- `severity`
- `isOperational`
- `channelId`
- `sendToSlack`
- `enableStackTrace`
### \_\_proto\_\_
Attach error prototypical inheritance
## handleError
Handles application errors and calls the ffLogger (sends log to console if not
in production and also to papertrail). Collects params and stack trace.
### Parameters
- `sender` Machine sending the error
- `method` method sending the error
- `message` error description
- `severity` which errorLevel (defined in error handler)
- `isOperational` is it an operational error or programmer error
- `channelId` slack channel to post to if error is critical
- `sendToSlack` should the message be sent to slack, even if not critical
- `enableStackTrace` send error message with or without stacktrace
Returns **void**
## isTrustedError
Takes in a custom error object and return a boolean. If the error
is an operation error it returns true, and returns false if it is a
Programmer error.
### Parameters
- `error`
Returns **any**
## on
Global handler for all uncaught exceptions
If an uncaughtException is not an Operational error,
gracefully restart the application.
## on
Global handler for all uncaught promise rejections.
If an uncaught promise rejection is thrown this will handle the error
gracefully.
## index
errorHandler module export.
Type: {AppError: [AppError][13], isTrustedError: (function (any): any), handleError: (function ([string][12], [string][12], [string][12], [boolean][14]): [Promise][15]&lt;([error][16] | never)>)}
[1]: #errorlevels
[2]: #apperror
[3]: #parameters
[4]: #__proto__
[5]: #handleerror
[6]: #parameters-1
[7]: #istrustederror
[8]: #parameters-2
[9]: #on
[10]: #on-1
[11]: #index
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[13]: #apperror
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error