How to report Node.js errors from AWS Lambda

We've created our own Node.js template to automatically monitor AWS Lambda functions for errors. In this post we'll teach you how to report errors from your own Lambda functions.

TLDR: We've created our own Node.js template to automatically monitor AWS Lambda functions for errors. Get the code here.

AWS Lambda allows you to invoke a custom function in response to events such as an HTTP request, a message from SNS, an S3 event, or even to perform arbitrary units of work. The functions themselves -- called handlers -- can be written in Node.js, Java, or Python. Today we're going to take a look at how to set up a Node.js handler and monitor it for errors using Honeybadger for Node.js.

The anatomy of a handler

In Node.js, a handler is a simple JavaScript function with two arguments: event and context. The handler should be assigned to exports.handler inside the file that is loaded by AWS. Consider the following example:

exports.handler = function(event, context) {
  if (event.success === true) {
    context.succeed("Hello world");
  } else {
    context.fail("Goodbye, cruel world.");
  }
};

The event argument is the data passed into the function from the event source, which could be anything from a Kenesis stream to an API request. When it reaches your function, the event is just a simple JSON object, and therefore can be totally arbitrary. In this example, we could test this function with the following event data to make it succeed:

{
  "success": true
}

The context argument is provided by AWS Lambda and contains useful information about the runtime in addition to some important functions: context.succeed() and context.fail(). In order to properly terminate your Lambda function's execution, you must call one of these functions to indicate a successful or unsuccesful result. Otherwise, your function will continue to execute until the Node.js event queue is empty or the configured timeout is reached.

Creating your first Lambda Function

The following is an example handler provided by AWS. It takes an event including options and request data and makes an HTTPS request to the provided endpoint:

var https = require('https');

/**
 * Pass the data to send as `event.data`, and the request options as
 * `event.options`. For more information see the HTTPS module documentation
 * at https://nodejs.org/api/https.html.
 *
 * Will succeed with the response body.
 */
exports.handler = function(event, context) {
    var req = https.request(event.options, function(res) {
        var body = '';
        console.log('Status:', res.statusCode);
        console.log('Headers:', JSON.stringify(res.headers));
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            body += chunk;
        });
        res.on('end', function() {
            console.log('Successfully processed HTTPS response');
            // If we know it's JSON, parse it
            if (res.headers['content-type'] === 'application/json') {
                body = JSON.parse(body);
            }
            context.succeed(body);
        });
    });
    req.on('error', context.fail);
    req.write(JSON.stringify(event.data));
    req.end();
};

If you have an AWS account you can try creating a new Lambda function from this template here. To finish creating your function, give it a name and select a role ("Basic execution role" should work). The default memory and timeout settings are fine.

The heart of a Lambda function's execution is the event payload, which is just a JSON object. The event object can contain any data that the function needs to execute. If you test your new function using the default test event data from the AWS console, you'll probably see an error like this:

START RequestId: 53a5a8e9-d043-11e5-aa29-3fb208bb8a4a Version: $LATEST
2016-02-10T22:12:09.104Z  53a5a8e9-d043-11e5-aa29-3fb208bb8a4a  TypeError: Cannot read property 'protocol' of undefined
    at Object.exports.request (https.js:100:14)
    at exports.handler (/var/task/index.js:11:21)
END RequestId: 53a5a8e9-d043-11e5-aa29-3fb208bb8a4a
REPORT RequestId: 53a5a8e9-d043-11e5-aa29-3fb208bb8a4a  Duration: 192.04 ms Billed Duration: 200 ms   Memory Size: 128 MB Max Memory Used: 10 MB  
Process exited before completing request

Oops! It looks like we're missing some options that our handler needs to make the HTTP request, and the function is throwing an uncaught TypeError because the event.options key is missing. To make this function actually send an HTTP request we need to provide it with valid options and data (optional):

{
  "options": {
    "host": "encrypted.google.com",
    "port": 443,
    "method": "GET",
    "path": "/"
   }, "data": {
     "hello": "world"
   }
}

To configure the test event, select "Actions" -> "Configure test event" while viewing your Lambda function in the AWS console. Running the test again with the correct options should cause your test to succeed.

So that's great! We can use this function to securely send arbitrary data to any HTTPS endpoint we want, on-demand; a great use case for this is delivering WebHooks.

Monitoring Lambda Functions for errors

But what about that first error we encountered? And what about other errors? This function is fairly brittle; if you forget a required event key it will crash; if the HTTP response is invalid JSON (or sends the wrong Content-Type header) it will also crash.

The good news is that AWS Lambda provides built-in monitoring through Amazon CloudWatch. It's relatively easy to get pretty charts on anything from average invocation counts, durations, and even errors. CloudWatch also allows you to create alarms when a metric exceeds a threshold, so you could set up an alarm to send an SNS message when the error rate for your function exceeds a certain level.

If you want more fine-grained notifications about errors, however, you're on your own. Luckily, Honeybadger has you covered.

We've developed our own template for creating a Lambda function which reports all unhandled exceptions to Honeybadger. You can get the code here.

We're relying on some 3rd-party dependencies via NPM (including our own honeybadger client package for Node), so we're using the advanced scenario ("upload a zip file") method to deploy our handlers.

Reporting errors to Honeybadger

First, clone our template repo:

git clone git@github.com:honeybadger-io/honeybadger-lambda-node.git ./lambda_func
cd ./lambda_func

Next we're going to modify the index.js file to make HTTP requests using the Node.js HTTPS function template from before:

console.log("Loading function");


// Change to your Honeybadger.io API key.
const HB_API_KEY = 'your api key';


var https = require('https');

/**
 * Pass the data to send as `event.data`, and the request options as
 * `event.options`. For more information see the HTTPS module documentation
 * at https://nodejs.org/api/https.html.
 *
 * Will succeed with the response body.
 */
function handler(event, context) {
  var req = https.request(event.options, function(res) {
    var body = '';
    console.log('Status:', res.statusCode);
    console.log('Headers:', JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      body += chunk;
    });
    res.on('end', function() {
      console.log('Successfully processed HTTPS response');
      // If we know it's JSON, parse it
      if (res.headers['content-type'] === 'application/json') {
        body = JSON.parse(body);
      }
      context.succeed(body);
    });
  });
  req.on('error', context.fail);
  req.write(JSON.stringify(event.data));
  req.end();
}


// Takes a handler function and returns a new function which reports errors to
// Honeybadger.
function makeHandler(handler) {
  var Honeybadger = require("honeybadger"),
      Promise = require("promise");

  var hb = new Honeybadger({
    apiKey: HB_API_KEY,
    logger: console      // Required for events to be emitted.
  });

  var send = function(err, opts) {
    return new Promise(function(resolve, reject) {
      hb.once("error", reject).
         once("remoteError", reject).
         once("sent", resolve);
      hb.send(err, opts);
    });
  };

  return function(event, context) {
    try {
      handler.apply(this, arguments);
    } catch(err) {
      send(err, { context: { event: event } }).then(function() {
        context.fail(err);
      }).catch(function(sendErr) {
        console.error("Unable to report error to Honeybadger:", sendErr)
        context.fail(err);
      });
    }
  }
}

// Build and export the function.
exports.handler = makeHandler(handler);

Don't forget to change the HB_API_KEY constant to the API key for your project in Honeybadger. If you don't have an account you can create a 30-day free trial here.

Save the new index.js, and then build the .ZIP file:

npm install
make build

To replace the old lambda function with your monitored function, select "Upload a .ZIP file" as your code entry type instead of "Edit code inline". Follow the instructions to upload the .zip file and then test your function again (perhaps with an empty test event payload: {}).

You should now see the original error in Honeybadger, in realtime!

Further reading:

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Joshua Wood

    Josh is Honeybadger's resident bug hunter and technical debt collector. Once enthusiastically referred to as a "human exception tracker", he spends his days crafting the middleware, plugins, and gems which keep the 'badger fat and happy through a steady diet of fresh data.

    More articles by Joshua Wood
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial