From 6341ad0bf8e4af1e22caf40ac5f37f57d04ed183 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Fri, 18 Nov 2016 14:33:52 +0100 Subject: [PATCH] Initial commit; v1.0.0 --- .gitignore | 2 ++ .npmignore | 1 + CHANGELOG.md | 3 ++ README.md | 63 ++++++++++++++++++++++++++++++++++ gulpfile.js | 18 ++++++++++ index.js | 3 ++ package.json | 30 ++++++++++++++++ src/index.js | 31 +++++++++++++++++ test/bluebird-promise-error.js | 18 ++++++++++ test/es6-promise-error.js | 17 +++++++++ test/reported-error.js | 15 ++++++++ test/sync-error.js | 15 ++++++++ 12 files changed, 216 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 index.js create mode 100644 package.json create mode 100644 src/index.js create mode 100644 test/bluebird-promise-error.js create mode 100644 test/es6-promise-error.js create mode 100644 test/reported-error.js create mode 100644 test/sync-error.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f028e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/lib/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..096746c --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..49399c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 (November 18, 2016) + +Initial release. diff --git a/README.md b/README.md new file mode 100644 index 0000000..17ca8e8 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# unhandled-error + +A small utility for tracking unhandled errors of all varieties. + +* Catches all unhandled Promise rejections. +* Catches all uncaught synchronous exceptions. +* Allows for reporting errors manually. +* Automatically crashes your process to prevent data loss or corruption. + +This library __only__ takes care of collecting errors. If you want to receive e-mail notifications when an unhandled error occurs, you'll probably want to use the higher-level [`report-errors`](https://www.npmjs.com/package/report-errors) tool instead. + +This library does not yet support browser usage, but this is planned for a future version. + +## License + +[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer. A donation and/or attribution are appreciated, but not required. + +## Donate + +Maintaining open-source projects takes a lot of time, and the more donations I receive, the more time I can dedicate to open-source. If this module is useful to you, consider [making a donation](http://cryto.net/~joepie91/donate.html)! + +You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. Thank you! + +## Contributing + +Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in `src/`, not those in `lib/`. + +Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes. + +Be aware that by making a pull request, you agree to release your modifications under the licenses stated above. + +## Usage + +```javascript +const unhandledError = require("unhandled-error"); + +let errorReporter = unhandledError((error, context) => { + logSomehow(error, context); +}); +``` + +## API + +### unhandledError(handler, [options]) + +Automatically collects all unhandled errors, and forwards them to the specified `handler` before crashing the process. + +* __handler:__ The callback to call for every unhandled error. This callback should somehow log the error, so that you can review it later. __To ensure safe operation, this callback *must* be fully synchronous.__ The handler callback receives the following callback arguments: + * __error:__ The actual error object that was reported. This will be unmodified. + * __context:__ The "context" in which the error occurs. This is an object that can contain *any* set of keys/values, or none at all - it's specified by the reporter. The one standard context value is `promise`, which is included for unhandled Promise rejections and indicates the Promise that the error originated from. You'll usually want to store the context verbatim, for later inspection. +* __options:__ + * __doNotCrash:__ If set to `true`, it prevents the library from crashing your process after an error is reported. This is *extremely dangerous*, and you should only use it if you are fully aware of the consequences. Defaults to `false`. + +Returns a new `errorReporter`, that you can report errors on. + +__WARNING:__ Note that *as soon as you call `unhandledError`*, it will start intercepting errors, and things like the default `uncaughtException` handler will no longer fire! + +### errorReporter.report(error, [context]) + +Manually reports an `error` with the given `context`. It will be treated the same as any automatically caught errors. + +* __error:__ The error to report. *Must* be an Error object or descendant thereof. +* __context:__ Any data associated with the context in which the error occurred. For example, when reporting an unhandled error from your Express error-handling middleware, you might want to include `req` and `res` here. There is no standard set of keys/values to use here - include whatever information you feel is important to reproduce the error, as long as it's JSON-serializable; circular references are fine. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..1769501 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,18 @@ +var gulp = require("gulp"); +var presetES2015 = require("@joepie91/gulp-preset-es2015"); + +var source = ["src/**/*.js"] + +gulp.task('babel', function() { + return gulp.src(source) + .pipe(presetES2015({ + basePath: __dirname + })) + .pipe(gulp.dest("lib/")); +}); + +gulp.task("watch", function () { + gulp.watch(source, ["babel"]); +}); + +gulp.task("default", ["babel", "watch"]); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..507257b --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require("./lib"); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6b55432 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "unhandled-error", + "version": "1.0.0", + "description": "Catches every uncaught error, whether synchronous or not, for easier logging", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://git.cryto.net/joepie91/node-unhandled-error.git" + }, + "keywords": [ + "error handling", + "errors", + "monitoring", + "promises" + ], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": { + "unhandled-rejection": "^1.0.0" + }, + "devDependencies": { + "@joepie91/gulp-preset-es2015": "^1.0.1", + "babel-preset-es2015": "^6.6.0", + "bluebird": "^3.4.6", + "gulp": "^3.9.1" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c55a981 --- /dev/null +++ b/src/index.js @@ -0,0 +1,31 @@ +'use strict'; + +const unhandledRejection = require("unhandled-rejection"); + +module.exports = function createUnhandledErrorHandler(handler, options = {}) { + function handleError(error, context) { + handler(error, context); + + if (options.doNotCrash !== true) { + process.exit(1); + } + } + + let rejectionEmitter = unhandledRejection(); + + rejectionEmitter.on("unhandledRejection", (error, promise) => { + handleError(error, { + promise: promise + }); + }); + + process.on("uncaughtException", (error) => { + handleError(error, {}); + }); + + return { + report: function reportError(error, context) { + handleError(error, context); + } + } +} diff --git a/test/bluebird-promise-error.js b/test/bluebird-promise-error.js new file mode 100644 index 0000000..b6d2186 --- /dev/null +++ b/test/bluebird-promise-error.js @@ -0,0 +1,18 @@ +'use strict'; + +const Promise = require("bluebird"); +const unhandledError = require("../"); + +unhandledError((error, context) => { + console.error("ERROR:", error); + console.error("CONTEXT:", context); +}); + +setTimeout(() => { + console.log("bar"); +}, 500); + +Promise.try(() => { + console.log("foo"); + nonExistentFunction(); +}); diff --git a/test/es6-promise-error.js b/test/es6-promise-error.js new file mode 100644 index 0000000..556d200 --- /dev/null +++ b/test/es6-promise-error.js @@ -0,0 +1,17 @@ +'use strict'; + +const unhandledError = require("../"); + +unhandledError((error, context) => { + console.error("ERROR:", error); + console.error("CONTEXT:", context); +}); + +setTimeout(() => { + console.log("bar"); +}, 500); + +Promise.resolve().then(() => { + console.log("foo"); + nonExistentFunction(); +}); diff --git a/test/reported-error.js b/test/reported-error.js new file mode 100644 index 0000000..aeaa00a --- /dev/null +++ b/test/reported-error.js @@ -0,0 +1,15 @@ +'use strict'; + +const unhandledError = require("../"); + +let reporter = unhandledError((error, context) => { + console.error("ERROR:", error); + console.error("CONTEXT:", context); +}); + +setTimeout(() => { + console.log("bar"); +}, 500); + +console.log("foo"); +reporter.report(new Error("Testing"), {foo: "bar"}); diff --git a/test/sync-error.js b/test/sync-error.js new file mode 100644 index 0000000..2bd0343 --- /dev/null +++ b/test/sync-error.js @@ -0,0 +1,15 @@ +'use strict'; + +const unhandledError = require("../"); + +unhandledError((error, context) => { + console.error("ERROR:", error); + console.error("CONTEXT:", context); +}); + +setTimeout(() => { + console.log("bar"); +}, 500); + +console.log("foo"); +nonExistentFunction();