Debugging Tips

Error pages generated by Workers

When a Worker running in production has an error that prevents it from returning a response, the client will receive an error page with an error code, defined as follows:

Error code Meaning
1101 Worker threw a JavaScript exception.
1102 Worker exceeded CPU time limit. See: Resource Limits
1015 Your client IP is being rate limited.


Other 11xx errors generally indicate a problem with the Workers runtime itself – please raise a support ticket if you see one.

Seeing errors in production / staging

Normally, you write and test your worker in the live preview. The preview includes a JavaScript console where you can see debugging info like console.log()s and uncaught exceptions. Hopefully, you can get your script working entirely in the preview before pushing.

However, due to technical limitations, the preview doesn’t always exactly produce what will happen in production. We therefore recommend deploying your script to a test or “staging” site before making it live on your real site.

If you run into problems in staging or production, debugging is harder. console.log() does not work here, and uncaught exceptions produce a generic error page (error code 1101). We are working to make this experience better, but for now, here are some tips for debugging a live site:

Return debug information in a header

A common quick hack to get some debug information out of your worker is to return it as a header value in the response.

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

async function handle(request) {
  let newUrl = myRewriteFunction(request.url)
  let response = await fetch(newUrl, request)

  // Copy the response so that we can modify headers.
  response = new Response(response.body, response)

  // Shove our rewritten URL into a header to find out what it was.
  response.headers.set("X-Debug", newUrl)

  return response
}

function myRewriteFunction(url) {
  return url + "?foo=bar"
}

Make subrequests to your debug server

A worker can make HTTP requests to any site on the public internet. Many projects already have a service like Sentry set up to collect error logs from browser-side Javascript. You can use the same service to collect errors from your Worker, by making an HTTP request to the service to report the error. Refer to your service’s API documentation for details on what kind of request to make.

When logging using this strategy, you must account for a small but important detail: normally, any outstanding asynchronous tasks are canceled as soon as a worker finishes sending its main response body back to the client. In order to ensure that a logging subrequest completes, you can pass its fetch() promise to event.waitUntil(). For example:

addEventListener('fetch', event => {
  event.respondWith(handle(event));
})

async function handle(event) {
  let request = event.request
  let newUrl = myRewriteFunction(request.url)

  // Without event.waitUntil(), our fetch() to our logging service may
  // or may not complete.
  event.waitUntil(postLog(newUrl))

  return fetch(newUrl, request)
}

function postLog(data) {
  return fetch("https://log-service.example.com/", {
    method: "POST",
    body: data
  })
}

function myRewriteFunction(url) {
  return url + "?foo=bar"
}

Wrap your whole event handler in a try/catch

If you’re getting error code 1101 from your Worker, that means it is throwing an exception. You can catch the exception in your code using a regular old try/catch block. (Note that this works best if your code is factored into an async function, otherwise you also need to know how to catch exceptions from promises.)

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

async function handle(request) {
  try {
    let newUrl = myRewriteFunction(request.url)
    return await fetch(newUrl, request)
  } catch (err) {
    // Display the error stack.
    return new Response(err.stack || err)
  }
}

function myRewriteFunction(url) {
  throw new Error("oops!")
}

Console-logging Headers

TL;DR: Use a Map if you just need to log a Headers object to the console:

console.log(new Map(request.headers))

Use the spread operator if you need to quickly stringify a Headers object:

let requestHeaders = JSON.stringify([...request.headers])

The Problem

When debugging worker scripts, we often want to examine the headers on a request or response. A common pitfall is to try to log headers to the developer console via code like this:

console.log(request.headers)

or this:

console.log(`Request headers: ${JSON.stringify(request.headers)}`)

Both result in what appears to be an empty object — the string {} — even though calling request.headers.has('Your-Header-Name') might return true. This is the same behavior that browsers implement.

The reason this happens is because Headers objects do not store headers in enumerable JavaScript properties, so the developer console and JSON stringifier do not know how to read the names and values of the headers. It’s not an empty object per se, but rather an opaque object.

Headers objects are iterable, however, which we can take advantage of to develop a couple quick one-liners for debug-printing headers.

Pass Headers objects through a Map

The first common idiom for making Headers console.log()-friendly is to construct a Map object from the Headers object, and log the Map object.

console.log(new Map(request.headers))

This works because:

  • Map objects can be constructed from iterables, like Headers.

  • The Map object does store its entries in an enumerable JavaScript property, so the developer console can see into it.

Spread Headers into an array

The Map trick works great for simple calls to console.log(), but if we need to actually stringify our headers, we quickly find out that stringifying a Map yields nothing more than [object Map], which isn’t very helpful.

The JSON stringifier can’t help us, either: even though our Map stores its data in an enumerable property, that property is Symbol-keyed, and JSON.stringify() ignores Symbol-keyed properties — we end up with an empty {} again.

Instead, we can take advantage of the iterability of the Headers object in a new way by applying the spread operator (...) to it.

let requestHeaders = JSON.stringify([...request.headers], null, 2)
console.log(`Request headers: ${requestHeaders}`)

This results in something like:

Request headers: [
  [
    "accept",
    "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
  ],
  [
    "accept-encoding",
    "gzip"
  ],
  [
    "accept-language",
    "en-US,en;q=0.9"
  ],
  [
    "cf-ipcountry",
    "US"
  ],
  // ...
]

While not as elegant as object literal syntax, this is certainly readable and useful for debugging purposes.