How to Debug Cloudflare Workers with AppSignal
Published: 2021-09-22 14:26:19

In this article, you'll learn how to capture error logs in your Cloudflare Workers application using AppSignal. We'll build a simple workers project and integrate AppSignal's code to collect the necessary metrics. We'll also learn how to utilize AppSignal's dashboard to analyze and track errors.

Let's get stuck in!

## What Are Worker Functions in Cloudflare Workers?
[Cloudflare Workers](https://workers.cloudflare.com/) is a serverless platform that allows developers to build and quickly deploy backend logic using JavaScript. Unlike most serverless platforms, worker functions can respond to a request in less than 500 milliseconds in cold start conditions. Unfortunately, building a worker application does get frustrating at times due to the difficulty of debugging the code.

Here's a snapshot of the metrics collected from a deployed workers application:

![01-cloudflare-workers-metrics](https://blog.appsignal.com/images/blog/2021-09/01-cloudflare-workers-metrics.png)

In the example above, the majority of the requests have passed, while a few have failed. Unfortunately, there's no way to dig into the dashboard for more information about errors.

The [recommended solution](https://developers.cloudflare.com/workers/examples/debugging-logs) is to use a remote logging service to capture error data. This service should have an intuitive dashboard that allows you to inspect the error messages captured.

## Prerequisites

This article assumes you have at least a beginner's experience in building a Cloudflare Workers application. That means you should have:

- A [Cloudflare Workers](https://workers.cloudflare.com/) account
- The latest version of [Wrangler CLI](https://developers.cloudflare.com/workers/get-started/guide) tool installed and authenticated to connect to your account

For ease in testing, we'll use the [Visual Studio Code](https://code.visualstudio.com/) editor and [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

Alternatively, you can use another code editor, but you'll have to use a tool like Postman to make GET and POST requests.

## Project Setup

You can find the source code for the project we'll use in this article on [GitHub](https://github.com/brandiqa/appsignal-debug-worker). If you would like to follow along, set up a new workers application from scratch using the following commands:

```bash
# scaffold project
wrangler generate appsignal-debug-worker

# navigate inside the project
cd appsignal-debug-worker

# install dependencies
npm install
```

We'll build a simple POST request handler that calculates a person's age based on their date of birth. This POST handler will accept date information via JSON. We'll use the [date-fns](https://date-fns.org/) package to handle date calculations.

Install the package:

```bash
npm install date-fns
```

Update `index.js` as follows:

```javascript
import { parseISO, differenceInYears } from 'date-fns'

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  if (request.method === 'POST') {
    return handlePostRequest(request)
  } else {
    return handleGetRequest(request)
  }
}

/**
 * Respond with hello worker text
 * @param {Request} request
 */
async function handleGetRequest(request) {
  return new Response('Hello worker!', {
    headers: { 'content-type': 'text/plain' },
  })
}

/**
 * Respond with 'Hello, {name}. You are ${age} years old!' in json
 * @param {Request} request
 */
async function handlePostRequest(request) {
  const body = await request.json()
  const { name, dob } = body
  // Calculate age
  const dobDate = parseISO(dob)
  const today = new Date()
  const age = differenceInYears(today, dobDate)
  const data = JSON.stringify({
    message: `Hello, ${name}. You are ${age} years old!`,
  })
  return new Response(data, {
    headers: { 'content-type': 'application/json' },
  })
}
```

Next, update the `wrangler.toml` file and ensure `type` has been set to `webpack`:

```toml
name = "appsignal-debug-worker"
type = "webpack"
usage_model = "bundled"

account_id = "<insert account id here>"
workers_dev = true
```

Create a new `rest.http` file and copy the following HTTP request commands:

```http
# Test GET request
GET http://localhost:8787/ HTTP/1.1

###

# Test POST - invalid request
POST http://localhost:8787/ HTTP/1.1

###

# Test POST - valid request
POST http://localhost:8787/ HTTP/1.1
Content-Type: application/json

{
  "name": "John",
  "dob": "1990-03-15"
}
```

You can now use the command `wrangler dev` to launch the dev server. As the REST client is installed in VS Code, you can easily hover over each request and click the `Send Request` link. Execute all three request commands and take note of the response.

Below is the output of the invalid request:

```bash
[2021-09-08 13:15:07] POST appsignal-debug-worker.brandiqa.workers.dev/ HTTP/1.1 500 Internal Server Error
SyntaxError: Unexpected end of JSON input at line 0, col 0
{
  "exceptionDetails": {
    "columnNumber": 0,
    "exception": {
      "className": "SyntaxError",
      "description": "SyntaxError: Unexpected end of JSON input",
      "preview": null,
      "subtype": "error",
      "type": "object",
      "value": null
    },
    "lineNumber": 0,
    "text": "Uncaught",
    "url": "undefined"
  },
  "timestamp": 1631096101393
}
```

As we are running on the dev server, we can easily take note of the error. However, once you publish your application, you won't be able to access this essential error information.

In the next section, we'll look at how you can integrate AppSignal to capture and send errors when your application runs in deployment.

## Javascript Integration with AppSignal

Let's start by [signing up](https://www.appsignal.com/) for a new 30-day trial account (no credit card required). If you sign up with your email address, you'll need to go through the usual verification process. Afterward, the process is as follows:

1. Create a new organization (or choose an existing one).
2. Pick a language — choose JavaScript.
3. Create an app — use `appsignal-debug-worker` for name and `staging` for environment.
4. Install the AppSignal JavaScript package. You'll be presented with your API key.
5. Next, you'll be given a snippet of code to confirm whether you've successfully integrated AppSignal.

Steps 1 to 3 are pretty straightforward.

Let's go through steps 4 and 5. In your terminal, install the AppSignal package and upload the API key:

```bash
# Install package
npm install @appsignal/javascript

# Upload api key
wrangler secret put APPSIGNAL_API # supply your api key in the next prompt
```

To complete step 5, update `index.js` as follows:

```javascript
import Appsignal from '@appsignal/javascript'

const appsignal = new Appsignal({ key: APPSIGNAL_API })

async function handleGetRequest(request) {
  appsignal.demo()
  return new Response('Hello worker!', {
    headers: { 'content-type': 'text/plain' },
  })
}
```

Run the `wrangler dev` command and execute the first GET request specified in your `rest.http` file. Your code should run as normal.

Go back to your AppSignal dashboard and navigate to the 'Errors' page. You should have received an incident report that looks similar to the following screenshot:

![03-hello-world-error](https://blog.appsignal.com/images/blog/2021-09/03-hello-world-error.png)

That's just a simple test to confirm that we've successfully integrated AppSignal into our project. In the next step, we'll look at how we can actually implement error reporting in existing code.

Remove the `appsignal.demo()` line before you proceed.

## How to Send Errors to Your AppSignal Dashboard

There are a couple of ways we can catch and send errors to our AppSignal dashboard using the JavaScript API. We can use the `appsignal.sendError(error)` error:

```javascript
try {
  // write main logic here
} catch(error) {
  appsignal.sendError(error)
}
```

A second option is to use the `appsignal.wrap()` function as follows:

```javascript
try {
  await appsignal.wrap(() => {
    // write main logic here
  })
} catch (e) {
  // return error response, the error
  // has already been sent to AppSignal
}
```

Here's a clearer example, involving asynchronous code:

```javascript
async function handlePostRequest(request) {
  try {
    const data = await appsignal.wrap(async () => {
      // write main logic here
    })
    // return success response
    return new Response(data, {
      headers: { 'content-type': 'application/json' },
    })
  } catch (error) {
    // return error response
    return new Response(error, {
      headers: { 'content-type': 'text/plain' },
      statusText: error,
      status: 500,
    })
  }
}
```

When you use the `wrap` method, any errors within the block are captured and automatically sent to your AppSignal dashboard.

Next up, we'll look at how we can send more error information.

## Using Breadcrumbs to Help Find Errors

[Breadcrumbs](https://docs.appsignal.com/front-end/breadcrumbs.html) can provide additional information about an error. The information is sent to your dashboard, which should help you with re-tracing the cause of the problem in your code.

The syntax is as follows:

```javascript
appsignal.addBreadcrumb({
  category: "", // e.g. "UI", "Network", "Navigation", "Console"
  action: "", // e.g "The user clicked a button", "HTTP 500 from http://blablabla.com"
  metadata: {} // key/value metadata in <string, string> format
})
```

Here's a more complete example that you can use to update `index.js`:

```javascript
/**
 * Respond with 'Hello, {name}. You are ${age} years old!' in json
 * @param {Request} request
 */
async function handlePostRequest(request) {
  try {
    const body = await request.json()
    const { name, dob } = body
    // Calculate age
    const dobDate = parseISO(dob)
    const today = new Date()
    const age = differenceInYears(today, dobDate)
    const data = JSON.stringify({
      message: `Hello, ${name}. You are ${age} years old!`,
    })
    return new Response(data, {
      headers: { 'content-type': 'application/json' },
    })
  } catch (error) {
    return handleError(error, {
      category: 'Backend',
      action: 'handlePostRequest',
      metadata: {
        fileName: 'index.js',
        message: error.message,
        foo: 'bar',
      },
    })
  }
}

function handleError(error, breadcrumb) {
  appsignal.addBreadcrumb(breadcrumb)
  appsignal.sendError(error)
  return new Response(error, {
    headers: { 'content-type': 'text/plain' },
    statusText: error,
    status: 500,
  })
}
```

After updating the file, launch the dev server and test all the HTTP REST URLs. The valid POST request should return the following similar response:

```http
HTTP/1.1 200 OK
date: Wed, 08 Sep 2021 10:16:57 GMT
content-type: application/json
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=qS42jXbBLK13PlG6m1o4sBYLw%2B%2Bbp79hiVQ%2BSR8hRI85U548xXWp3NCG7T6vIDMUQcMMDkG%2FZPOM0elIzB3vs1UXWNUiLQkErgzi%2FgzpnBSubg%2FxLjOSj6lO4osFYCxe9UL1x691hEBEQAHz1ku9hgc17r7jjMA3WrhndnaT"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
vary: Accept-Encoding
cf-ray: 68b76c9d5dadd73d-DAR
content-encoding: gzip
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400, h3-28=":443"; ma=86400, h3-27=":443"; ma=86400
server: cloudflare
transfer-encoding: chunked
{
  "message": "Hello, John. You are 31 years old!"
}
```

The invalid POST request will output the same as before.

Let's now take a look at our AppSignal dashboard and what information got captured. The above type of error is placed under the `SyntaxError` entry. See the screenshot below for comparison:

![05-appsignal-breadcrumbs-view](https://blog.appsignal.com/images/blog/2021-09/05-appsignal-breadcrumbs-view.png)

As you can see, all the data we included in the `addBreadcrumbs` function has been captured. Implementing this strategy in your code will help you debug your code in production better.

## Troubleshoot Your Errors Easily with AppSignal

To recap, we've learned how to:

- Integrate AppSignal into our workers code using the `@appsignal/javascript` npm package
- Use the `appSignal.sendError()` function to send error data
- Use the `appSignal.wrap()` function to automatically capture and send error data
- Use the `breadcrumbs` function to send additional details about an error

While AppSignal provides a myriad of features, including performance and anomaly tracking, the `@appsignal/javascript` package only supports the **error reporting** feature.

Even so, error reporting allows you to build and deploy your worker applications with confidence. You'll be able to quickly troubleshoot problems *before* end-users submit support tickets.

**P.S. If you liked this post, [subscribe to our JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.**

**P.P.S. If you need an APM for your Node.js app, go and [check out the AppSignal APM for Node.js](https://www.appsignal.com/nodejs).**

*Michael Wanyoike writes clean, readable, and modular code. He loves learning new technologies that bring efficiencies and increased productivity to his workflow.*