commit 6341ad0bf8e4af1e22caf40ac5f37f57d04ed183 Author: Sven Slootweg Date: Fri Nov 18 14:33:52 2016 +0100 Initial commit; v1.0.0 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();