UNPKG

node-err

Version:

Simplify error handling and logging for Node.

378 lines (324 loc) 9.6 kB
let config = { status: 500, overrideResponses: true, prefix: 'SERVER_ERROR', logger: defaultLogger, template: null, debug: false, }; /** * Optional setup function. * * Used to apply custom options. * * @param {object} opts Containing all data. * @param {number} opts.status Default error status code. * @param {string} opts.prefix Prefix for error name. * @param {func} opts.logger Logging handler function, accepts err argument. * @param {boolean} opts.debug Debug mode. * @param {array} opts.responses Array of response template properties. * * @return {undefined} */ const setup = function(opts={}) { if (opts.responses && typeof opts.responses === 'object' && opts.responses.length && opts.responses.length > 0) { opts.template = opts.responses; } config = { ...config, ...opts, }; } /** * Error reporter. * * Mutates/adds error information to error object. * Reports the error to the console (or custom logging func). * * @param {object} err Main error object. * @param {object} opts Options object. * @param {string} [opts.name] Name of error. * @param {object} [opts.req] Express request object. * @param {boolean} [opts.log] Optionally skip logger. * @param {boolean} [opts.censor] Optionally censor req body logging. * @param {number} [opts.status] Http status to return. * @param {object} [opts.context] Custom data to attach to error. * @param {object} [ops.responses] Data to apply to response template. * * @return {undefined} Nothing returned; */ const report = function(err, opts) { if (err._reported) { return; } let { name='UNREPORTED', status=config.status, context=null, req=null, log=true, censor=false, } = opts; err._reported = true; err._name = config.prefix + ' - ' + name; err._status = status; err._context = context; err._censor = censor; err._time = Math.floor(Date.now()); err._response = generateResponse(config.template, opts.responses); if (req) { err._ipAddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress; err._reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl; err._reqBody = !err._censor ? req.body : 'CENSORED'; err._reqMethod = req.method; err._userAgent = req.headers['user-agent']; } if (log) { config.logger(err); } return; } /** * Error responder. * * Mutates/adds response information to error object. * Automatically overrides the original error responses and status. * * @param {object} err Main error object. * @param {object} opts Options object. * @param {boolean} [opts.log] Optionally skip logger. * @param {boolean} [opts.censor] Optionally censor req body logging. * @param {object} [opts.req] Express request object. * @param {number} [opts.status] Http status to return. * @param {object} [opts.responses] Data to override/apply to response template. * * @return {undefined} Nothing returned; */ const respond = function(err, opts) { let modified = false; let { responses=null, status=null, req=null, log=true, censor=false, } = opts; if (status) { err._status = status; modified = true; } if (censor) { err._censor = censor; modified = true; } if (responses) { err._response = generateResponse(config.template, opts.responses); modified = true; } if (req) { err._ipAddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress; err._reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl; err._reqBody = !err._censor ? req.body : 'CENSORED'; err._reqMethod = req.method; err._userAgent = req.headers['user-agent']; modified = true; } if (log && modified) { config.logger(Object.assign(err, { _name: 'RESPONSE_OVERRIDE' })); } return; } /** * Main error handling utility. * * Use this in a catch block. Accepts args for invoking * one of two different behaviors: error re-throwing * or custom value handling. * * @param {(object|*)} arg Main error object or custom value. * @param {(object|boolean)} arg2 Options object or boolean. * * @param {string} [arg2.name] Name of error. * @param {object} [arg2.req] Express request object. * @param {number} [arg2.status] Http status to return. * @param {object} [arg2.context] Custom data to attach to error. * * @return {(object|func)} Promise rejection or function. */ const repeat = (arg, arg2) => { if (arg instanceof Error) { let opts = (typeof arg2 === 'undefined') ? {} : arg2; return handleError(arg, opts); } else { let addReport = (typeof arg2 === 'undefined') ? false : true; return handleCustom(arg, addReport); } } /** * Error re-thrower. * * Re-throws the error with a debug logging option. * * @param {object} err Main error object or custom. * @param {object} opts Options object. * @param {string} [opts.name] Name of error. * * @return {object} Promise rejection. */ const rethrow = function(err, opts) { if (config.debug) { console.log('Error re-thrown at: ', (opts.name || null)); } return Promise.reject(err); } /** * Handle an error. * * Handles the error reporting and rethrow. * * @param {object} err Main error object or custom. * @param {object} opts Options object. * @param {string} [opts.name] Name of error. * @param {object} [opts.req] Express request object. * @param {number} [opts.status] Http status to return. * @param {object} [opts.context] Custom data to attach to error. * * @return {object} Promise rejection. */ const handleError = function(err, opts) { if (!err._reported) { report(err, opts); } else if (config.overrideResponses) { respond(err, opts); } return rethrow(err, opts); } /** * Handle custom value. * * Handles the error reporting and custom value. * * @param {*} customVal Custom value. * @param {boolean} addReport Add reporting/logging or not. * * @return {func} Callback function. */ const handleCustom = function(customVal, addReport) { return (err, opts={}) => { if (err._reported) { if (config.overrideResponses) { respond(err, opts); } return rethrow(err, opts); } if (addReport) { let name = (opts.name || err.name || ''); report(err, { ...opts, name: name += '; SILENCED;', status: '0' }); } return (typeof customVal === 'undefined') ? err : customVal; } } /** * Stop promisification of an error (no re-throw). * * Reports the error if not reported, but does not re-thow. * * @param {object} err Main error object or custom. * @param {object} opts Options object. * @param {string} [opts.name] Name of error. * @param {object} [opts.req] Express request object. * @param {number} [opts.status] Http status to return. * @param {object} [opts.context] Custom data to attach to error. * * @return {undefined} Nothing returned; */ const stop = function(err, opts={}) { // return just the error (no promise) if (err._reported) { if (config.overrideResponses) { respond(err, opts); } return; } report(err, opts); return; } /** * Generate an error response for the error. * * @param {object} err Main error object or custom. * * @return {object} Error Response Object */ const generateResponse = function(template, responses) { if (!template || !responses) { return null; } let response = template.reduce(function (obj, item) { obj[item] = (responses && responses[item]) ? responses[item] : null; return obj; }, {}); return JSON.stringify(response); } /** * Get status code of an error. * * @param {object} err Main error object or custom. * * @return {number} Status code */ const getStatus = function(err) { return err._status || config.status; } /** * Get response template for an error. * * @param {object} err Main error object or custom. * * @return {obj} Response object to surface. */ const getResponse = function(err) { return err._response ? JSON.parse(err._response) : getStatus(err); } /** * Add padding to error logging. * * @return {string} Line break string for cleaner output. */ const pad = function(n=30, s='*') { let ln = [...Array(n)].reduce(ln => ln + s, ""); return "\n" + ln + "\n"; } /** * Default logger. * * @param {object} err Main error object or custom. * * @return {undefined} Nothing returned; */ function defaultLogger(err) { console.warn( pad(), err._name, err, pad() ); } // exports module.exports = { setup, repeat, stop, getStatus, getResponse, logger: defaultLogger };