How to Debug JavaScript Errors
Debugging JavaScript errors in a production environment can be a difficult experience. More often than not, the error reports are vague, and identifying the underlying causes can be difficult at best. That said, there are a few common steps that can be followed towards identifying and resolving errors that crop up in production.
Gathering information
Step 1: Attempt to replicate circumstances
In software development, the first step towards debugging any issue is attempting to replicate the circumstances. With most programming languages, this is bolstered by reviewing logs leading up to an error, but with client-side JavaScript, this type of diagnosis requires significantly more foresight (more on that below).
Before we can replicate any circumstances of an issue, and assuming we have access to any production logs, we first need to establish some testing guidelines. This involves doing things like mimicking the production database, the user accounts involved, and even the operating system. Everything is fair game here.
Step 2: Test assumptions
Once you've established the circumstances that you think might throw the exception or error you are hunting down, it's time to test them. Never test exceptions in production. Development and staging environments are designed to be breakable without any impact on the end users, so always always always try to break your code in a safe environment.
Step 3: Increase logging
More information is always better. Using the methods described in Where are JavaScript Errors Logged?, the first step towards diagnosing any issue is to increase the amount of data you are logging. This allows you to see everything that is happening before and after a problem occurs. There is a good chance that the problems you are experiencing have potential warnings associated with them that don't necessarily make it into the logs by default.
Step 4: Adjust test parameters and try again
If you were unable to replicate the problem in Step 2, then it's back to the drawing board. Not every error is easy to reproduce, and may have time-based constraints or something else making it difficult to replicate in a non-production environment. Jump back to Step 1, adjust your test parameters, and try it all over again.
What are Source Maps?
In a production environment, JavaScript files are often compressed. While this is a great way to speed up any application, it can make debugging a nightmare, as the files you are trying to debug are practically unreadable. This is where source maps come into the picture. Supported by most modern browsers, a source map is a file that maps uncompressed code to compressed code, allowing you to see exactly what is going on within a production environment.
While most minification tools have built-in support for source maps, it is important to understand exactly how the browser connects compressed code to uncompressed code. In a nutshell, this comes in the form of a comment within a compressed JavaScript file that points directly to the source map.
//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map
As an example, let’s take a look at the developer console without source maps enabled. In particular, do you see how the source file says "Generated by CoffeeScript" and there is automatically generated JavaScript below?
Developer Console Debugger
While the production JavaScript file can be viewed and debugged it's not the code I wrote. The original source file is written in CoffeeScript, which means that any insight gleaned from the compiled code will have to be translated back into the original source file.
Now, let’s take a look at the same example with source maps enabled:
Developer Console Debugger
With source maps enabled, the breakpoint in the compiled script can be mapped directly to the original source file in CoffeeScript. This makes it possible to debug issues in the context of the source files, rather than the compiled and sanitized files that make it into the production environment.
Stack Traces in JavaScript
Whenever exceptions are thrown, a stack trace is usually included. But, what is a stack trace? In essence, it is a rundown of every file and function that is called leading up to the error. To be clear, a stack trace doesn't include the files and functions that are touched before the error occurred, only the chain of methods that are called as the error happened. This allows you to "trace" the "stack" of operations that are performed when an error happened in order to identify exactly what went wrong, and where.
As an example, let's take a look at the stack trace that is returned from the following (incredibly simplistic) code:
function do_the_thing() {
throw new Error("a thing happened!");
}
do_the_thing();
When do_the_thing() is executed, an exception is immediately thrown. This results in the following stack trace:
Error: a thing happened!
Stack trace:
do_the_thing@debugger eval code:2:9
@debugger eval code:1:7
As you can see, rather than simply returning the exception message, reading the stack trace in reverse order shows that the exception was thrown on line 1 at column 7, but was triggered by a call to do_the_thing() on line 2 at column 9. For more complicated stack traces, this can be invaluable as it gives us a lot of post-mortem information.
A Brief Introduction to The Debugger
While diagnosing bugs via log files works in some cases, sometimes a little more power is required. Thankfully, in most browsers it is possible to pause running scripts and step through them in order to identify issues as they happen. While doing this, it is possible to view and edit variables and properties, which gives you total control over the state of any running script in order to identify what is happening, where, and why.
Developer Console Debugger
The browser-based debugger is an incredibly powerful tool, but the basic idea behind its use is that you add breakpoints at points in your codebase where you want to look deeper at the stack and scope of the script. When the debugger encounters these breakpoints, all execution is paused, and you are provided with the opportunity to review the values of any variables, and even modify them to better test assumptions and fix any issues you may be encountering.
Advanced Debugging
It is important to note that, while the built-in debugger is great for many use cases, the introduction of more advanced JavaScript platforms like Vue.js and Redux can make the debugger significantly more complicated to use. This is where browser-based plugins like Vue.js devtools and Redux DevTools come into play. These tools are designed with the complexities of their targeted platforms in mind, allowing for much more targeted debugging of highly complex codebases.
Debugging Node.js
While the browser debugger is great for client-side JavaScript, Node.js ships with its own debugger for server-side debugging. Debugging Node.js code can be accomplished by running node inspect on the source file, or through any number of great IDE integrations designed specifically for Node.js debugging.
Visual Studio Code Node.js Debugger
Debugging Production Errors Using Rollbar
The challenge when debugging production errors is that you do not have access to the user’s developer console. In order to gather enough context to identify the problem, you need to track information in a central location where your developers can access it. Traditionally, this is done using logs but there is a better way.
Rollbar empowers you to not only identify what is happening, but when, where, to whom, and how often. Rather than having to sort through pages of raw text logs, Rollbar aggregates data from individual exceptions and errors and creates a dashboard with as much information as possible for analyzing these issues.
When properly configured, these exceptions can be tied directly to user accounts, and tracked over time across an easy-to-read graph—with deployment markers to boot. While this doesn't necessarily tell you exactly what an issue is, it comes as close as possible to doing so by providing you with more information than you could possibly ask for.
Telemetry
Unique to Rollbar's JavaScript integration, telemetry is an advanced event tracker that allows you to identify what happens in the moments leading up to, and immediately following, an error. This is incredibly valuable, as it gives you the ability to see any behavior that may have inadvertently lead to the error being thrown.
Occurrences
Whenever an exception is thrown during the course of an HTTP request, the request details are tracked alongside any additional information—such as the browser and operating system. This gives you the ability to identify any potential issues that could be related to specific systems, or even track down offending request parameters.
Suspected Deploy
If you are tracking deployments within Rollbar, then identifying which deployment might have been responsible for a new error or exception is as straightforward as possible. So straightforward, in fact, that Rollbar does the work for you.
Source Control Integration
When a Rollbar project is linked to a repository in GitHub, Bitbucket, or GitLab, any file references in a stack trace are automatically linked directly out to the offending file in the linked repository.