No Description

Sven Slootweg e5bce45095 Initial commit; v1.0.0 3 years ago
src e5bce45095 Initial commit; v1.0.0 3 years ago
.gitignore e5bce45095 Initial commit; v1.0.0 3 years ago
.npmignore e5bce45095 Initial commit; v1.0.0 3 years ago
CHANGELOG.md e5bce45095 Initial commit; v1.0.0 3 years ago
README.md e5bce45095 Initial commit; v1.0.0 3 years ago
gulpfile.js e5bce45095 Initial commit; v1.0.0 3 years ago
index.js e5bce45095 Initial commit; v1.0.0 3 years ago
package.json e5bce45095 Initial commit; v1.0.0 3 years ago
screenshot.png e5bce45095 Initial commit; v1.0.0 3 years ago
test.js e5bce45095 Initial commit; v1.0.0 3 years ago

README.md

report-errors

An easy-to-use tool for tracking unhandled errors.

  • Automatically and immediately e-mails you when an unhandled error occurs in your application.
  • Easy to set up.
  • Supports both synchronous and asynchronous errors (using Promises).
  • Includes both a simplified stacktrace and full error details in the e-mail.
  • Supports manual reporting of errors (eg. from error-handling middleware).
  • Automatically crashes the process after reporting an error, to prevent data loss or corruption.
  • Reporting is handled from outside of your application process, using a dedicated daemon - this minimizes the chance of data loss.
  • Configurable e-mail subject line.
  • Works with any SMTP provider, as well as directly from the server (but read the caveat below).

An example of an error report:

Screenshot of report e-mail

Crashing processes and safe error handling

By default, report-error will crash your process once it has encountered and reported an error, in the expectation that a service manager will restart it. While this behaviour can be disabled, I strongly recommend against that.

When an unhandled error occurs, that means that your application was not aware of how to handle this error - thus, it also cannot know what application state was affected by this error. The application is now in an undefined state, and continuing to run in an undefined state could lead to data loss, corruption, or security issues.

The only safe thing to do after encountering an unhandled error, is for your application to crash as soon as possible, and be restarted cleanly. For this reason, you should leave the default behaviour intact if at all possible, and rely on your service manager (systemd, forever, PM2, etc.) to restart the application.

If you are concerned about downtime, you can run your application in cluster mode, or as multiple processes behind a load balancer. This way, if a single process crashes, new requests will simply be redirected to other processes while your crashed process is restarting.

Sending reports without an SMTP provider

While report-error can send reports directly to a specified e-mail address without using an external SMTP server, this is not recommended in most cases. Spamfilters will generally distrust these kind of "directly-sent" e-mails, for a variety of reasons. You can make it work by explicitly setting a "this is not spam" rule on the receiving side, but it's generally easier to just use an SMTP provider of some sort.

License

WTFPL or CC0, 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!

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

This library consists of two parts:

  1. The error handling library
  2. The reporting daemon

The error handling library is purely responsible for catching all unhandled errors in the application, dumping them to a JSON file on disk, and subsequently crashing the process. It does not attempt to do any formatting or post-processing. The reporting daemon picks up the dumped files (by watching the filesystem), formats them into e-mails with attachments, and sends them off.

Creating a configuration file

The reporting daemon uses a single configuration file to determine where to send reports. A typical configuration file might look something like this:

{
    "errorPath": "/opt/my-project/errors",
    "stackFilter": "*",
    "metadata": {
        "from": "ops@cryto.net",
        "to": "admin@cryto.net"
    }
}

For the sake of this documentation, we'll assume that you've saved this file as reporter-config.json, but you can pick any filename - you'll have to provide its path explicitly when you run the error reporter binary, either way.

Valid options are:

  • errorPath: The directory that your errors are stored in. You will configure this later in the error handling library as well. This can be any path, but generally you'll want it to be an errors subdirectory in your project directory.
  • stackFilter: What modules to filter out of the simplified stacktraces shown in the e-mail report. This can either be the string "*" (to filter out every third-party module), or an array of module names to filter. Note that the e-mail will always include a JSON attachment containing the full stacktrace - this setting purely affects the e-mail body.
  • subjectFormat: The format for the subject line of the report e-mail. In this string, $type will be replaced with the error type/name, and $message will be replaced with the error message.
  • metadata:
    • from: The sender address displayed on the e-mail report.
    • to: The address to e-mail reports to.
  • smtp: Optional. The SMTP server to use for sending the e-mail reports. If not configured, e-mails are delivered directly to the recipient's e-mail server (but see the caveat above).
    • hostname: The hostname on which the SMTP server can be reached.
    • port: The port number that the SMTP server is accessible on.
    • username: Your username for the SMTP server.
    • password: Your password for the SMTP server.
  • nodemailer: All optional. Custom options to be passed directly into the underlying nodemailer instance.
    • secure: Forces SSL/TLS usage.
    • requireTLS: Forces STARTTLS usage.
    • ignoreTLS: Prevents usage of either SSL/TLS or STARTTLS.
    • tls: Custom TLS options to pass to the tls core library.
    • localAddress: The local network interface to use for network connections.
    • connectionTimeout: How many milliseconds to wait for a connection to be established.
    • greetingTimeout: How many milliseconds to wait for the SMTP greeting to be received.
    • socketTimeout: How many milliseconds of inactivity to allow, before a connection is closed.

Setting up the daemon

The exact setup instructions will vary depending on what service manager you use, but you should ensure that the following command is run somehow:

/path/to/your/project/node_modules/.bin/report-errors /path/to/your/reporter-config.json

That's it! The daemon will watch the configured error directory, and e-mail you any errors that appear. Make sure that your service manager is configured to restart the process if it crashes.

You will probably also want to configure your service manager to log the output of the application - if something breaks in the reporting daemon, it will be printed to the terminal. It can't e-mail you the error if the reporter itself is broken!

Setting up the library

Using the library is pretty simple - simply add the following at the start of your application's entry point:

const path = require("path");
const reportErrors = require("report-errors");

// ...

let errorReporter = reportErrors(path.join(__dirname, "errors"));

This will store all errors in the errors subdirectory relative to your entry point (which is usually, but not always, the root of your project). You should ensure that your reporter configuration file has this same path set as its errorPath.

In some cases, you might want to report unhandled errors manually - for example, from Express error handling middleware. You'd use the report method for that, like so:

/* Assuming `app` contains an Express application, and the `errorReporter` has been defined as before... */

app.use((error, req, res, next) => {
    if (error.statusCode != null && error.statusCode >= 400 && error.statusCode < 500) {
        /* This is a client error, such as a 401 or a 403. */
        res.status(error.statusCode).send(error.message);
    } else {
        /* This is some other kind of error we didn't expect. */
        errorReporter.report(error, {
            req: req,
            res: res
        });
    }
})

Note how we're not just passing in the error, but also an additional object containing req and res - this object is the context object, and it can contain any JSON-serializable data. It's stored alongside the error, and it can be useful to more easily reproduce an error - for example, in this case, the serialized version of the req object will include things like the request method and path.

Building your own error processing tools

While the default error reporting daemon is good enough for most simple deployments, in some cases you may want to develop your own tooling for dealing with the reported errors. All report-errors errors are stored on disk in a standardized JSON format:

  • error: The serialized error object. Usually includes at least name, message, code, and stack (as well as custom properties), but none of these are guaranteed to exist. The stacktrace is the original string-formatted stacktrace, and it's up to you to parse it as needed.
  • environmentName: A brief description of the environment that the error occurred in - operating system, runtime, and so on. As provided by the env-name module.
  • environmentInfo: A structured set of information about the environment that the error occurred in. As provided by the env-info module.
  • hostname: The hostname of the system on which the error occurred.
  • context: An object containing the "context" that was passed in with the reported error. For an uncaught rejection, this will contain the originating Promise as a promise property, but the context object may contain any kind of context that the reporting code felt was necessary to reproduce the error. This information is meant for later human inspection.

The JSON attachments in the default reporter's e-mails include the above properties, but also several additional properties:

  • parsedStack: A parsed version of the stacktrace, as produced by the stacktrace-parser module.
  • simplifiedStack: A simplified version of the parsed stacktrace, simplified according to the stackFilter option in your reporter configuration. All removed stacktrace items are replaced by an object of the format {type: "ignored", count: 9}, where the count property indicates how many stacktrace items were omitted.

API

reportErrors(errorPath, [options])

Creates a new errorReporter instance.

  • errorPath: Path where all errors will be stored. This must be writable by the application, and it must be the same path that the configuration file for the reporting daemon points at.
  • 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.

WARNING: Note that as soon as you create an instance, 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.