DEV Community

Cover image for Debugging beyond console.log
Cornelia
Cornelia

Posted on

Debugging beyond console.log

In the past 10 years, I’ve worked with many other web developers and noticed something odd: many devs do not use the full arsenal for debugging in the browser - no matter what level they are at.

For this article I’ve set up a simple application on stackblitz you can use to follow along as we explore how to debug using breakpoints, watch, the Call Stack and various console methods for improved logging. Finally, we will also look into debugging with redirects.

Enjoy.

Demo application

Image description

The app is a modified version of W3School’s “How TO - Create a To Do List” app. It is a simple to-do app that lets you add texts via input field and button to a list of existing to-dos. You cannot add empty or duplicate values to the list. After adding an item, the input field is cleared. Clicking on a list item toggles its done-state. Each entry also has a button to delete it.

The button below the list redirects the user to another stackblitz app that redirects to google. It’s initially disabled but we will use it at the end of the tutorial to look into debugging despite page loads or redirects.

For the first sections, we only need the rendered app and the developer tools.

Open the demo app to follow along.
Can’t wait to see the code? Check out the app in edit mode.

Breakpoints

Do you ever wonder what happens to a value in a function that calls twenty other functions to modify it? Do you then find yourself logging it after each and every one of them? You can use breakpoints for these and other situations. A breakpoint will halt the execution of a script allowing you to check exactly what is going on, which value variables have and what is available in that scope. You can then follow the script step by step to the end of execution.

Standard breakpoints

To do so, open your developer tools and navigate to “Sources”. Locate the JavaScript file that includes the line of code you want to stop at. In this case, you are looking for top/js-cl89rs.stackblitz.io/~/index.js. Alternatively, you can click on index.js:69 in the console next to the “Let’s debug!” output for quicker access.

To add a breakpoint simply click on the line number. It will be highlighted in blue and the line of code will show up in the breakpoints section on the right. There you can also deactivate it. Remove breakpoints either by clicking their highlighted line number or via the context menu in the breakpoints section to the right.

Image description

Now add a breakpoint on line 53, the first check within the validateNewTodo function. If you now add a new to-do, the execution will stop when it reaches the breakpoint. You can then hover over the variable, use the console and access everything available within the scope you’re currently at. You can also see available functions and variables in the “Scope” section on the righthand side.

Image description

To simply continue execution use the button “Resume script execution” (resume) on the right or on top of the rendered app.
If you want to follow the execution step by step, use “Step over next function call” (step over). Stepping over will not follow the execution into functions but will let you check a value before and after it’s executed.
If you want to follow it into function calls, use “Step into next function call” (step into) instead. If you step into the filter function on line 56, you will see each value being checked. Now, if you don’t want to keep following the execution for each to-do, you can use “Step out of current function” (step out) to skip the rest of the execution of the filter method and continue to the next line.

Image description

Conditional breakpoints

Breakpoints are great, but what happens if we want to check only a specific type of item, one of - let’s say - one thousand? You’re probably thinking, “Well, I just add an if with a console.log”. That’s one way to do it, but you could also use Conditional Breakpoints. These breakpoints will only stop the execution of the script if a logical check resolves to true.

Right-click your breakpoint on line 53 and select “Edit breakpoint…”. If you already removed the breakpoint, right-click on the line number and select “Add conditional breakpoint…”. Fill in your condition, for example, val == 'stop'.Now the script will only halt if the value you want to add is “stop”.

Image description

Logpoints

If you only need a simple log, you can also add a logpoint instead of modifying the code of the application. Edit your breakpoint again and choose “Logpoint” from the dropdown and add what you want to log to the input.

Image description

Watch

If you want to keep track of a specific variable, especially when it is in global scope, it might be hard to keep track of it within the Scope section; maybe a property of window. In this case, you can use the “Watch” section.

The app keeps track of how many to-dos you have in your list and stores it in window.todoCount. To keep an eye on what happens to the list, hit the plus in the header of the “Watch”-section and enter “window.todoCount”. You now see the current amount of to-dos in your list.

Remove or disable all your breakpoints. Now add or remove one or more to-dos. The watched variable will not update automatically, but you can reload it with the “refresh” icon next to the plus. If you re-enable or add a breakpoint at line 60 and then step over the addTodo function call, you can see that the watch now updates automatically.

Image description

Call Stack

Many functions are called by a lot of different other functions. Sometimes it’s tricky to figure out where a specific call came from. To check that we can look at the Call Stack.

The renderTodos method is called from four different places within the app. Let’s find out which ones by using the Call Stack.

Add a breakpoint at the top of the renderTodos function in line 14 and then reload the page. On the right in the “Call Stack” section you can see that you currently are at renderTodos in index.js:14, indicated by the arrow to the left of the line. If you now click on the line below you jump to the anonymous function that called it. If you go further down the Call Stack you can look at the inner workings of stackblitz. Resume the script.

Image description

One down, only three to go. Now add a new to-do to your list. Notice, this time the Call Stack is a lot smaller. We can see that the function was called by addTodo which was called by validateNewTodo. Look at the Call Stack when deleting an entry and when toggling the done status.

Console methods

Besides log, there are many other useful methods that you can use to output information. Feel free to fork the app and add the different outputs as we work through the methods.

Fork the demo app on stackblitz. Since stackblitz doesn’t forward the output of all methods to the integrated console, open the app in a separate tab (“Open in New Window - live” button at the top right).

Image description

.info vs .log

As an alternative to log, there is the info method. Some browsers, like Chrome, treat both the exact same way. They consider them at the same level and render them in the same style. Other browsers, like Firefox, differentiate between the two. In Firefox, you can toggle their visibility separately and info output comes with a little information icon.

Image description

Why bother? Most devs will configure their linter to not accept console.log, if you want to log (relevant) information you can still use the info method. This way, you will always know log-outputs are dev only, info-output can stay.

.warn & .error

As indicated by their names, these are to output warnings and errors. They each have a separate logging level that you can show or hide in the console.

Let’s include them in our application. Let’s output a warning before a to-do is deleted by adding console.warn(‘Deleting todo’, todos[i].text); at line 32. Within the validateNewTodo function let’s output errors in both cases the validation fails.

Image description

.table

When logging arrays of objects, it can be daunting to check a specific property on multiple entries. To easily compare these, you can use the table method. Try it out for yourself by adding console.table(todos); to the addTodo function.

Image description

.assert

We’ve learned about conditional breakpoints, but how about conditional logs without an if? You can use the assert method and pass a condition as the first and the message to log as a second argument. The assert will output an error when the condition renders false.

Image description

.time & .timeEnd

Another useful thing you can do directly with console methods is checking how long the execution of functions takes. To do that, add console.time(‘new-todo’); at the beginning of the validateNewTodo function. The string we pass is the label of the timer. Now add console.timeEnd(‘new-todo’); before both returns and at the end of the function to ensure the timer is stopped even if the validation fails.

Image description

.count & .countReset

You can use the count method to track how often the script runs a certain part of the logic. In the demo application, the function renderTodos is called multiple times. Add console.count(‘render-todos’); at the top of the function. The string we pass acts as the label of the counter. If you now reload the app and add, toggle and remove to-dos, you’ll see an output with the label and the number of times the line has been run. You can reset the counter by calling console.countReset(‘render-todos’);.

Image description

.trace

You can also easily output the Call Stack to the console by calling the trace function.

Image description

.group & .groupEnd

By this point, the console gets full quickly. To keep a better overview, you can group the output. To do that, simply add console.group(‘add-todo’); at the beginning and console.groupEnd(‘add-todo’); at the end of the addTodo function. The string is the label of the group that will be shown at the top, where you can toggle it open or closed.

Add another group to the renderTodos function. Since the addTodo function calls renderTodos, the groups are nested.

Image description

You can check out even more methods in the mdn web docs.

Debugging with redirects

Debugging an application that redirects can be tricky, but there are a couple of things that can help us.

First remove the disabled attribute on the button in the index.html file. If you now click the button below the to-do list, it will redirect you to another stackblitz project that logs something and redirects to google. However, feel free to redirect to another page directly.

If you know where the redirection happens, you can add a breakpoint at the trigger. Now you can use the Call Stack to check what happens up to this point within the application. However, if you try to step into or over the replace call, you’ll notice that the script will not pause on the new page. If this page also redirects, it can be especially hard to check what exactly happens. In this case, watching the window.location can be useful, but it will also update once the script resumes.

You can preserve the console output and the network. The log will at least list all URLs you’ve been redirected to and the network tab will show you which resources have been loaded, including scripts that caused further navigation.

Image description

Conclusion

What you should take away:

Next time you need to debug something and find yourself constantly going back to log various variables, think of this article. Likely, one or more of the things you’ve learned working your way through it will speed up the process. Good luck and happy debugging!


Thanks for reading.

Top comments (0)