Capturing Alexa Errors with Sentry and GitLab

November 18, 2020  |  6 minutes to read


Diagnosing issues with a live Alexa skill can be tricky.

The Amazon Echo logo with a speech bubble saying "Uh oh"

Most users who run into issues will simply uninstall your skill. A few unusually inspired users may even leave helpful reviews like this:

An Amazon review that says &qout;This skill is broken&qout;

How do you go about figuring out what’s wrong?

By plugging a few open source tools together, you can get great visibility into what’s going wrong.

1. Implement an ErrorHandler in your skill code

First, you’ll need a way to globally catch errors in your Alexa skill. The ASK SDK provides an ErrorHandler interface that does just this. Create a new file for your ErrorHandler implementation:

// lambda/src/handlers/ErrorHandler.ts

import * as Alexa from 'ask-sdk-core';

export class ErrorHandler implements Alexa.ErrorHandler {
  canHandle() {
    // Handle _all_ exceptions
    return true;
  }
  handle(handlerInput: Alexa.HandlerInput, error: Error) {
    console.log(`~~~~ Error handled: ${error.stack}`);

    const speech = 'Sorry, something went wrong! Can you please try again?';

    return handlerInput.responseBuilder
      .speak(speech)
      .reprompt(speech)
      .getResponse();
  }
}

(I’m using TypeScript in this example, but a vanilla JS implementation shouldn’t be much different.)

Next, register this error handler in your skill’s entrypoint:

// lambda/src/index.ts

import * as Alexa from 'ask-sdk-core';
import { ErrorHandler } from './handlers/ErrorHandler';

export const handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(/* ...your request handlers here...  */)
  .addErrorHandlers(new ErrorHandler()) // ← add this
  .lambda();

This already gets you pretty close! If anything goes wrong, you’ll have a nice stack trace in your CloudWatch logs, and the user will get a polite message informing them something went wrong.

2. Ship error details to Sentry

Sentry is an open source monitoring platform that does a great job of tracking and organizing software errors.

After creating a free account at https://sentry.io, create a new Sentry project with the “Node.js” platform option. Install the dependencies it recommends:

yarn add @sentry/node @sentry/tracing

Next, set up @sentry/node with the tracking info it needs. You can do this at the beginning of every Alexa request by creating a new request interceptor:

// lambda/src/interceptors/SentryInterceptor.ts

import * as Alexa from 'ask-sdk-core';
import * as Sentry from '@sentry/node';

export class SentryInterceptor implements Alexa.RequestInterceptor {
  async process() {
    Sentry.init({
      dsn: '<your DSN here>',
      tracesSampleRate: 1.0,
    });
  }
}

(Your Sentry DSN will be provided to you when setting up your Sentry project.)

Don’t forget to register this interceptor, similar to how you registered your ErrorHandler above:

// lambda/src/index.ts

import * as Alexa from 'ask-sdk-core';
import { ErrorHandler } from './handlers/ErrorHandler';

export const handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(/* ...your request handlers here...  */)
  .addRequestInterceptors(new SentryInterceptor()) // ← add this
  .addErrorHandlers(new ErrorHandler())
  .lambda();

Finally, in the error handler you created earlier, send the error to Sentry:

// lambda/src/handlers/ErrorHandler.ts

import * as Alexa from 'ask-sdk-core';
import * as Sentry from '@sentry/node'; // ← add this

export class ErrorHandler implements Alexa.ErrorHandler {
  canHandle() {
    // Handle _all_ exceptions
    return true;
  }
  handle(handlerInput: Alexa.HandlerInput, error: Error) {
    console.log(`~~~~ Error handled: ${error.stack}`);

    Sentry.captureException(error); // ← also add this

    const speech = 'Sorry, something went wrong! Can you please try again?';

    return handlerInput.responseBuilder
      .speak(speech)
      .reprompt(speech)
      .getResponse();
  }
}

That’s it! Now all that’s left is to…

3. Test it!

In your LaunchRequest handler, do something silly like this:

const anObject: any = {};
anObject.aMethodThatDoesntExist();

(I’m explicitly specifying any here, otherwise TypeScript won’t let me get away with this!)

Deploy your skill and give it a spin!

❯ ask dialog
User  > Open <your skill name here>
Alexa > Sorry, something went wrong! Can you please try again?

Jump back into your Sentry project - you should now be the proud owner of a new issue:

A screenshot of the Sentry dashboard with a new issue


4. Integrate Sentry with GitLab (optional)

If you host you skill’s code on GitLab you can take advantage of GitLab’s first-class Sentry integration to see error details directly in your GitLab project:

  1. From your GitLab project, navigate to Settings > Operations
  2. Expand the Error tracking section
  3. Check the Active checkbox
  4. Assuming you are using Sentry’s hosted solution, enter https://sentry.io/ in the Sentry API URL field
  5. Paste your Sentry auth token into the Auth Token field. To generate an auth token in Sentry:
    1. Navigate to your Sentry dashboard
    2. Click the ▼ next to your name and select API keys
    3. Click Create New Token
    4. Leave the default scopes as they are and click Create Token
    5. Copy the big string of gibberish
  6. Jump back to GitLab and click Connect and select your Sentry project
  7. Click Save changes

That’s it! Navigate to Operations > Error Tracking. You should see the same set of Sentry errors nicely displayed inside your GitLab project.

A screenshot of GitLab's Error Tracking page showing an issue's details


Source

See this code in action at https://gitlab.com/nfriend/days-until, or give my Days Until skill a try for yourself.


Other posts you may enjoy:

Ridiculous Refs

October 19, 2019  |  2 minutes to read

PDF Gotchas with Headless Chrome

April 15, 2019  |  6 minutes to read

The Next Chapter

December 4, 2018  |  Less than 1 minute to read

ES6 Object Literal Shorthand Is Fun And Kind Of Scary

November 16, 2018  |  2 minutes to read

Shell-ing With TypeScript

June 17, 2018  |  2 minutes to read