Debugging is an essential step in software development, as it allows developers to fix errors before releasing the software to the public. Debugging tools can be integrated into code editors, making the debugging process more efficient. This tutorial will show you how to debug Node.js in Visual Studio Code (a.k.a VSCode).
What is Node.js?
Node.js is a JavaScript runtime environment that allows you to run JavaScript code outside a browser. It is not a framework but a technology built on Chrome’s V8 JavaScript engine, allowing it to be used for developing server-side applications and command-line tools. It is also popular on the Internet of Things (IoT). Node.js has a large community of developers who have created a variety of modules that you can use in your own projects.
Node.js apps are applications written in JavaScript that are executed on a server using the Node.js runtime environment. These applications are often built on popular JavaScript frameworks and libraries such as Express and Meteor.
The main features of Node.js are:
It is an open-source server-side JavaScript runtime environment.
Node.js is a cross-platform runtime environment that allows you to run JavaScript code on the server-side.
Applications are written in JavaScript and can be run within the Node.js runtime on OS X, Microsoft Windows, and Linux.
Applications are single-threaded, meaning that a single process can handle multiple requests simultaneously.
It is asynchronous, meaning that it can handle multiple requests simultaneously without waiting for each one to finish before starting the next.
It uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
It is available under the MIT license.
While all the above is fantastic, there are bugs where there is code. Debugging becomes a common task for every developer as a side effect.
Step-by-step guide on debugging Node.js in VSCode
Prerequisites
Before we begin this tutorial, we will assume that you have both Node.js and VSCode installed.
For this tutorial, we are going to set up a simple Node.js program with an error in it.
We will then follow a debugging process that will give you the basic scope of how to use the Node.js debugger in VSCode.
To create our simple program, run npm init inside an empty project directory in your console. This will give us a package.json file.
In the same directory, create an app.js file with the following code:
const calculate = (A, B) => {
console.log('Adding...')
let sum = 0
sum = A + B + B + A
return sum
}
const num1 = 1
const num2 = 1
const result = calculate(num1, num2)
console.log(num1 + ' plus ' + num2 + ' = ' + result)
We know this is incorrect. There is a deliberate bug introduced in the sum variable. If we run the program with the command node app.js, we’d get the following output:
Adding...
1 plus 1 = 4.
Using the Node.js debugger in VSCode
VSCode comes with a built-in debugging tool that developers can use to debug Node.js applications. This tool is located on the left-hand side panel and looks like this:
Alternatively, you can also use the keyword shortcut Ctrl + Shift + D to switch to VSCode’s debugging panel.
Next, click create and launch.json file and select Node.js.
You will see a configuration that looks something like this:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\src\app.js"
}
]
}
Sometimes, depending on your file directory setup or version, the assigned value to the program may not match the entry main file to your application. For that particular situation, you just need to change it to whatever your main entry file is – which is typically something like app.js or index.js.
You can also configure the Node.js debugger to skip certain files listed under the skipFile array.
Once you’ve completed this, you can save and exit the file. This will change the way the VSCode debugging side panel looks. Here is the updated screenshot.
When you click the play button next to Run, it will launch and run the program through the Node.js debugger. The debugger console will run and exit with the standard output of Process exited with code 0, which means everything worked as expected.
The process of debugging a Node.js app
To debug your app.js file in VSCode, you can set breakpoints by clicking on the red dot when you hover over the line number you wish to put it. A breakpoint is a place where the VSCode debugger intentionally stops or pauses the program’s execution so that you can inspect state changes in the variables and watch and call stack panels located on the left-hand side of the UI.
Now, when you rerun the program, it will continue down the code and stop at where you placed the red dot. Looking over the variables panel will log out the current state of the values assigned. Simply right-click on the red dot and select Remove breakpoint to get rid of a breakpoint that you no longer need.
Here, our A and B variables are set with values, but the result variable is undefined because it is yet to be defined in the program – meaning that it doesn’t exist yet.
The VARIABLE panel outputs all the current variables and their states.
Meanwhile, the WATCH panel lets you cherry-pick specific variables that you want to keep an eye on. This panel is helpful because it gives you instant insight into what is happening with the code.
If you look to the top of the UI, you will also encounter a control panel that looks something like this:
This panel controls the VSCode debugger’s progression through the program. Each control does the following:
Continue (F5) will run past the breakpoint and continue with the rest of the program until it hits the next breakpoint.
Step over (F10) will take the debugger down to the next line.
Step into (F11) will take the debugger into the next function.
Step out (F12) will take the debugger out from the function and into the next step.
Restart (Ctrl+shift+F5) will relaunch the VSCode debugger.
Stop (shift+F5) will stop debugging and exit the process being debugged.
When you click on one of the above controls, you are essentially slowing down the program so you can see the updates occur in the console, the variables, watch, and call stack panels.
Going back to our code sample, it will take us to the calculate() function if we click Step Into. If we look at the VARIABLE panel, we see that the values in it have changed to reflect the local variables currently in scope. When you right-click on a variable, you can select it as a target to watch. Setting this will add the variable to the WATCH panel as per below.
If you click Step Over, it will run the following line. Our console will simply say Adding… because it’s executed the console.log() call. However, the watched variable has now changed from undefined to 0.
If we run the following line, we will see that the sum variable inside watch has changed to an incorrect value. You can deduce that something wrong occurred in the previous line. We can now fix the issue, rerun the entire VSCode debugger, and ensure everything is working.
Debugging Node.js in production with Lightrun
While debugging Node.js code during development is easy enough in VSCode, it’s a different kind of ball game when debugging in production. When there’s a bug in production, we cannot simply shut down the server and take the code down to debug. Not only that, replicating production bugs in a development environment may not be possible. This is where a tool like Lightrun can help.
Black Friday, the traditional kickoff to the holiday shopping season, is set to make waves in 2024 with projected sales reaching an impressive $10.8 billion—a 9.9% increase from last year according to Statistics.blackfriday analysts. According to the same team, Cyber Monday sales in 2024 are expected to reach $13.2 billion—a 6.1% increase from 2023.
Both events in sum are expected to generate $24 billion in sales. A major contributor to this growth is the increasing number of consumers who prefer to shop online. As consumer behaviors evolve to favor online shopping, retailers are expected to invest more in their online infrastructure, user experience, and platform reliability to meet this projected demand.
More traffic means more potential revenue, but it also means more queries, more transactions, and more potential problems. As an online retailer, you need to be well-prepared from a technical standpoint to ensure that you don’t miss out on any potential sales.
Amazon Prime Day 2018: A Prime Example For How Things Can Go Wrong
Q4 is a critical time for businesses, and developers are often under immense pressure to ensure that their systems are up and running. The last thing you want is for your website to crash during the busiest shopping season of the year. Poor performance means lost revenue, and problems can quickly escalate into a PR nightmare.
You’ve likely heard about the infamous Amazon Prime Day crash in 2018, which reportedly cost the company an estimated $100 million in lost sales. Despite the charm of the “Dogs of Amazon” error pages, they couldn’t mitigate the damage. Cute error messages are no substitute for a robust, well-prepared platform.
Some unofficial post-mortems of the Prime Day crash suggest that the root cause was a failure in the auto-scaling system, which left Amazon scrambling to manually add server capacity. The internal system “Sable,” Amazon’s e-commerce storage and computation platform, struggled under the surge—which led to a failure across the platform. Some experts speculate that misconfigurations or software bugs in scaling systems magnified the issue, forcing Amazon to temporarily shut down international traffic to the site according to CNBC.
In all cases, the lesson is clear: impredictable issues can arise, and you need to be prepared.
If you google “How to prepare for Black Friday for developers,” you’ll find a plethora of articles that will tell you to “test your system,” “monitor it,” “add autoscalability to your infrastructure,” and “secure your code.” While these may be good pieces of advice, they touch only the surface of the problem as they are not shifting left enough to prevent the issues from happening in the first place.
In sports, for example, achieving peak performance isn’t the result of a single training session but rather a dedicated regimen of practice, evaluation, and adjustment over time. Similarly, preparing for Black Friday is not a one-time event, but rather a continuous process that requires iterative improvements based on feedback. The earlier this feedback is received, the better.
Shift-Left Everything: The Early Bird Catches the Worm
Performance should be a first-class citizen in your development process. That’s why it should not be an afterthought, but rather a core part of your development cycle. A software development lifecycle includes several stages, such as planning, coding, testing, and deployment. You should be thinking about performance from the early stages of development. The problem is that performance testing is often associated with the staging and production environments—which is too late in the development cycle.
By shifting performance testing to the left, you can identify and fix performance issues early in the development process. This approach allows you to catch performance bottlenecks before they become critical issues.
Performance problems can be triggered by a variety of factors, such as bad capacity planning, downsized infrastructure, inefficient code, slow database queries, extreme edge cases, or code that doesn’t scale well. All these issues can be identified early in the SDLC if you have the right tools and processes in place.
For example, with Lightrun, you can dynamically add a log to a specific function suspected of causing slow database queries, without halting or redeploying the application. This enables you to pinpoint the exact query, analyze its execution time, and resolve the issue during the development phase, long before it reaches staging or production.
Using Lightrun’s snapshot feature, you can also capture the state of your application at a specific point in time. Let’s say you have an issue that only occurs under certain conditions (e.g., a specific event or input). You can capture the state of your application after reproducing the issue using a snapshot that can be added directly from the Lightrun IDE plugin. This snapshot not only captures the trace of the application but also the state of the variables at that point in time. This allows you to deeply analyze the issue from the inside and fix it accordingly. You can also export the snapshot and share it with your team members for further analysis. All of this happens from the comfort of your IDE while you’re coding. The best part is that you capture logs and traces from any environment, including your development, testing, staging, and production environments without any code changes on the remote server.
Another crucial, often overlooked practice you should consider is observability.
Observability is the ability to understand what’s happening inside your system based on external outputs. An e-commerce platform is responsible for different types of operations, including user authentication, product search, transaction processing, and more. Observability allows you to monitor these operations and understand how they are performing from the user’s perspective. Using logs, metrics, and traces, developers can gain useful and actionable understanding of their applications’ and infrastructure’s behavior.
Nowadays, observability is a key practice, but its implementation is often not quite right in many organizations. First, not understanding what to “observe” can lead to a lot of noise and irrelevant data – a sheer volume of data makes it challenging to identify the root cause of a problem. Second, observability is often associated with the production environment, which is a late phase in the SDLC. The first problem creates a siloed approach to observability where developers are less likely to use the data to improve their code. The second problem leaves developers out of the loop when it comes to understanding how their code behaves in production.
Instead of this approach, observability should be a part of the development process from the beginning. But here’s the catch: if developers have to wait for a new deployment on the testing or staging environment to add a log or a metric, a new bottleneck is created. A solution to this, besides having a continuous deployment pipeline, is to use a tool that allows them to instrument their code on the fly without the need to redeploy their applications. This is also known as “dynamic instrumentation” and it’s a key feature of Lightrun.
According to a 2018 Stripe report, developers spend an average of 17.3 hours per week on maintenance and debugging issues. This amounts to $300 billion in lost developer productivity every year. By shifting left observability, performance testing, and debugging, you can reduce this number and save valuable time and resources.
Debugging in Production: No Longer a Meme
Shifting left observability, performance testing, and debugging is the secret sauce to a successful Black Friday. Small incremental changes with continuous feedback and early feedback are the ingredients to a successful recipe. However, incidents can still happen, and when they do, you need to be able to debug in production as close to the problem as possible.
Some years ago when someone mentioned “debugging in production“, it was often seen as a joke or the worst-case scenario. Today, it’s a regular practice for many organizations. The reason is simple: the production environment is the most complex and the most realistic environment. If a bug or a performance issue escapes your team despite all the early stages of testing, the early feedback, and the continuous improvement, you need to be able to debug it in production. Chances are that the issue is an edge case that only occurs in production.
Traditionally, debugging in production was a scary and unsafe process. The fastest way to do it was SSHing into the server and adding a print statement in the code. This is not only unsafe but also inefficient. It’s unsafe because you’re modifying the code in the production environment, which can lead to unexpected results. It’s inefficient because you need to redeploy the application to see the changes, which can take time and may disrupt the user experience.
Here’s when Lightrun once again comes to the rescue. A developer can add a line of log to the code in the live production environment without halting the application or redeploying it. Imagine the following scenario: you have a performance issue that only occurs in production. You can add a log to the suspected function or line of code, capture the logs, and analyze them in real-time to identify the root cause of the problem. Similarly, you can add a snapshot to capture the state of the application at a specific point in time and under specific conditions.
All you have to do is to install the Lightrun plugin in your IDE – available for IntelliJ, PyCharm, WebStorm, VS Code and select web IDEs – connect it to your production environment, and start debugging. It’s that simple.
Reduce the Mean Time to Resolution (MTTR)
It may be too late to prevent all performance issues from happening, but you can still reduce the time it takes to resolve them. The Mean Time to Resolution (MTTR) is a key KPI that measures the average time it takes to resolve an issue. The longer it takes to resolve an issue, the more revenue you lose.
To understand how to calculate the MTTR, let’s consider the following formula:
MTTR = (Total time to resolve issues) / (Total number of repairs
If you have a high MTTR, it means that you’re spending too much time resolving issues. Serious engineering teams aim to reduce the MTTR as much as possible. In general, there’s no good or bad value for the MTTR; this depends on the context of the organization, the criticality of the application, and the complexity of the issues. But as a rule of thumb, if your team is spending more than 3 hours per issue, you may want to consider improving your processes. This is especially true during Black Friday when every minute counts. According to ECDB, as an example, online shopping peak hours in some countries (e.g., Germany, UK) are between 8 pm and 10 pm. If your MTTR is 3 hours and your application crashes at 8 pm, your team will only start to resolve the issue at 11 pm. This means that you were down during all the peak hours.
Lightrun can help you reduce your MTTR. For example, Lightrun capabilities have enabled InsideTracker developers to diagnose and resolve application issues in real-time, saving valuable debugging time. Their MTTR improved by 50%, resulting in dozens of hours saved each month. Prior to using Lightrun, InsideTracker developers were unable to troubleshoot or access remote Kubernetes environments from their local machines in an easy, straightforward way. This led to long and inefficient debugging sessions that required hotfixes and redeployments taking hours.
What’s Next?
Lightrun provides a suite of powerful features that help developers quickly identify and resolve issues in production environments but before reaching production, Lightrun can also help you shift-left observability, testing, and debugging.
The tool offers a range of features that can help you improve your development process, including:
Live debugging: Developers can add logging statements and metrics to their code in real-time, without having to redeploy or restart their applications.
On-demand snapshots: Lightrun Snapshots provide virtual breakpoints but without stopping the execution of your application. You can add conditions, evaluate expressions and inspect any code.
Custom metrics: Right from the IDE, developers can create custom metrics to track specific aspects of the system’s behavior, such as response time or error rates. All tasks are completed without the need to redeploy any code.
Integration with existing developers and enterprise tools: Lightrun integrates with existing cloud-native tools, such as Prometheus, Grafana, VSCode, public cloud providers, and more, making it easy to integrate into existing workflows.
Lightrun empowers not only developers but also DevOps, SREs, Platform Engineers, and other stakeholders to improve their development process and as a result, catch issues early, reduce the MTTR, and improve the overall performance of their applications.
Ready for Black Friday 2024? If not, Lightrun is here to help:
Get a Lightrun demo and learn how to easily debug microservices, Kubernetes, Docker Swarm, ECS, Big Data, serverless, and more: https://lightrun.com/get-a-lightrun-demo/