Module 0334: Debugging NodeJS programs

Tak Auyeung, Ph.D.

September 30, 2021

Contents

 1 About this module
 2 Why using a debugger?
 3 NodeJS inspection
  3.1 A server-client model
  3.2 A per-user port number
 4 How to use
  4.1 Starting the development/debug web server
  4.2 Attaching an inspector to a web serving script
 5 Debugging techniques
  5.1 Documenting a symptom
  5.2 Find out how to reproduce a symptom
  5.3 Determining where to set break points
  5.4 Finding and debugging infinite loops
  5.5 Checking values of variables

1 About this module

2 Why using a debugger?

A debugger is a tool for inspecting various aspects of a program while the program runs. Most debuggers can do the following:

3 NodeJS inspection

While most other languages use the term “debugger”, NodeJS uses the term “inspector” to refer to the same tool.

Because NodeJS is built for web serving, the inspection tool is, correspondingly, suitable for web serving.

3.1 A server-client model

NodeJS inspector uses a server-client model. This allows the convenient attachment and detachment of a debugger to a program is that running with minimal service interrupts. Furthermore, this model allows the debugging of a web serving program that is running and with end-user interaction.

A “server” is a program in execution that listens for requests, whereas a “client” is a program that requests services from a server. In NodeJS inspection, the “server” is the NodeJS script that is serving web pages, whereas a “client” is a separate program in execution.

Because NodeJS inspection uses a server-client model, it also needs to specify an IP address and a port number. By default, the IP address is 127.0.0.1, (aka localhost), and the port number is 9229. The defaults are convenient on a system that has a single NodeJS program being debugged, but it is unsuitable for a system where many NodeJS programs run simultaneously.

3.2 A per-user port number

In an environment where multiple developers can be debugging NodeJS programs, it is best to assign a specific inspection port number per user. On the Power server, because each user already has a unique proxy port number, we can utilize that port number as a basis.

For example, if a user owns port number 41257, the right-most two digits identifies the user. As such, the port number 41357 is reserved for the same user for the development/debug web server. Using the same method, port 9357 is reserved for this user for NodeJS inspection.

4 How to use

4.1 Starting the development/debug web server

In the past, starting the developmennt/debug web server uses the following command:

pm2 start express.js --name express.debug

In order to enable the inspector at a specific port, the command is now more complicated:

pm2 start --node-args --inspect=127.0.0.1:9357 express.js --name express.debug

Note that port 9357 is an example, you need to find out what the right-most two digits are for your account.

From the perspective of requesting your debug/development app from a browser, nothhing is changed. At this point, you can sign out of the Power.

4.2 Attaching an inspector to a web serving script

When you want to debug your development/debug app, you can sign in to the Power server again. You can now attach an inspector to the web serving app that is already running using the following command:

node inspect 127.0.0.1:9357

Once the inspector is attached, you can use the commands of the inspector. The inspector has a built-in help message, type help to read the help messsage. Or, you can refe to the Debugger Manual for explanations and examples.

5 Debugging techniques

5.1 Documenting a symptom

In the debugging process, there are several reasons why documenting a symptom is important.

First of all, having a document that specifies the conditions and the symptom enables “regression tests”. This means an app can be retested in the future to see if a past bug resurfaces.

Secondly, the documentation process often helps to isolate the exact conditions leading to a particular symptom. This process, in return, is the first step to isolate the defact and eventually lead to a correction.

5.2 Find out how to reproduce a symptom

The first time a symptom occurs, it can be difficult to isolate the conditions that lead to the symptoms because few people keep track of exactly what they did prior to the discovery of a symptom when none is expected.

In the age of ample hard disk storage, however, this process can be aided by screen recording the testing of an app. If no symptoms occur, the recording of a test session can be deleted. However, if a symptom is to occur, the recording can be replayed to find out exactly what lead to the symptoms.

Most apps rely on a database, and as such, some symptoms may occur only if the database has specific data. This means it is crucial to make a back up of everything, including the content of the entire database, prior to conducting any test. In the event that a symptom is discovered, the original state of the database can be restored for future testing.

5.3 Determining where to set break points

It is important to understand how to determine where to set up break points.

First of all, a break point should be set up prior to the code producing the symptom of a bug (defect). This also depends on the structure of control structure statements.

If a break point is set up too far ahead of the code that is producing a symptom, you may need to single step a long way before getting to the problematic code. On the other hand, if a break point is set up too close to the code that is producing the symptoms, then you may have missed the actual cause of the symptom.

5.4 Finding and debugging infinite loops

An infinite loop can be difficult to debug without a debugger because an app may have many loops, and any one of them can be cause the symptoms of having an infinite loop.

The NodeJS inspector has a command called pause. As the name implies, this command pauses the execution of a program. After an app is paused, the inspector can be used, interactively, to determine where the paused code is located, and how it is potentially nested within levels of function calls and/or control structures.

For this purpose, another command, backtrace (or abbreviated as bt) can be very useful. The nesting of control structure statements can be analyzed by visually inspecting the code. However, the nesting of function calls at the time of the pause is dynamic and more difficult to analyzer. backtrace indicates the “stack” of function calls leading to the pause point, and hence greatly simplify the analysis of “how did we get here”.

5.5 Checking values of variables

Comparing the approach of using console.log in a program versus using an inspector, the ability of an inspector to interactively evaluate expressions is important. Being able to interactively evaluate expressions means that via an inspector after code is paused, a developer has the options to inspect variables.