Beyond console.log(): 7 Powerful Debugging Tips

Go beyond console.log() with console.table, console.trace, console.time, and 4 more powerful JavaScript debugging techniques every developer should master.

·8 min read
JavaScriptdebuggingconsole APIdeveloper toolsweb development
Console
AllErrorsWarnings
> console.table(users)
#nameroleactive
0"Alice""admin"true
1"Bob""user"false
> console.time("api-call")
> await fetch("/api/data")
> console.timeEnd("api-call")
api-call: 142.5ms
> console.trace("processOrder called")
processOrder called
at processOrder (checkout.ts:42)
at handleSubmit (form.ts:18)
at onClick (button.tsx:7)
> console.assert(response.ok, "API error:", 500)
Assertion failed: API error: 500

Every JavaScript developer starts with console.log(). It's the universal debugging tool -- quick, easy, and always available.

But the Console API has much more to offer. Here are seven techniques that can dramatically speed up your debugging and make your console output actually useful.

1. console.table() -- Visualize Structured Data

When you're inspecting arrays of objects, console.log() gives you a collapsed tree that you have to click through. console.table() renders it as a sortable, readable table -- instantly scannable.

const users = [
  { name: "Alice", role: "admin", active: true },
  { name: "Bob", role: "user", active: false },
  { name: "Charlie", role: "user", active: true },
];
console.table(users);

Pro tip: Pass a second argument to select which columns to display:

console.table(users, ["name", "role"]);
// Only shows the name and role columns

This is particularly useful when debugging API responses with many fields -- you can focus on just the properties you care about.

2. console.trace() -- Find Where Functions Are Called

When a function is called from multiple places, console.trace() shows you exactly how you got there. This is invaluable for understanding unexpected code paths.

function processOrder(order) {
  console.trace("processOrder called");
  // ... order processing logic
}

The output shows the full call stack, so you can see whether processOrder was called from the checkout flow, the admin panel, or an event handler you forgot about.

When to use it: You see a function executing with unexpected arguments, and you need to trace back who called it.

3. console.time() -- Measure Performance

Need to know how long an operation takes? Wrap it in a timer instead of calculating Date.now() differences manually:

console.time("api-call");
const response = await fetch("/api/data");
const data = await response.json();
console.timeEnd("api-call");
// Output: api-call: 142.5ms

You can run multiple timers simultaneously -- just give each a unique label:

console.time("total");
console.time("fetch");
const res = await fetch("/api/users");
console.timeEnd("fetch"); // fetch: 89.2ms

console.time("parse");
const users = await res.json();
console.timeEnd("parse"); // parse: 3.1ms
console.timeEnd("total"); // total: 92.7ms

4. console.group() -- Organize Related Logs

When you have related logs scattered across your code, grouping them keeps the console organized and readable:

console.group("User Authentication");
console.log("Token:", token);
console.log("Expiry:", expiry);
console.log("Permissions:", permissions);
console.groupEnd();

Use console.groupCollapsed() to start the group collapsed by default -- perfect for verbose debug output that you only need to expand when investigating an issue.

console.groupCollapsed("API Response Details");
console.log("Status:", response.status);
console.log("Headers:", Object.fromEntries(response.headers));
console.log("Body:", data);
console.groupEnd();

5. console.assert() -- Log Only When Something Is Wrong

Instead of wrapping console.log() in an if statement, use console.assert() to only print when a condition is false:

console.assert(response.ok, "API returned error:", response.status);
// Only prints if response.ok is falsy

console.assert(user.id, "User object missing ID:", user);
// Only prints if user.id is falsy

This is great for sanity checks that you want to leave in the code during development without cluttering the console during normal operation.

6. console.count() -- Track Execution Frequency

How many times does a function actually get called? Instead of manually incrementing a counter variable, use console.count():

function handleClick(buttonId) {
  console.count(`click-${buttonId}`);
}
// click-submit: 1
// click-submit: 2
// click-cancel: 1

This is especially useful for detecting unexpected re-renders in React:

function UserProfile({ user }) {
  console.count("UserProfile render");
  return <div>{user.name}</div>;
}

If you see "UserProfile render: 47" when you expected 2, you've found a performance problem.

Use console.countReset("label") to reset a counter back to zero.

7. Styled Console Output -- Make Logs Stand Out

Make important logs visually distinct with CSS styling:

console.log(
  "%c WARNING: Cache expired",
  "color: orange; font-weight: bold; font-size: 14px;"
);

console.log(
  "%c SUCCESS %c Order processed",
  "background: green; color: white; padding: 2px 6px; border-radius: 3px;",
  "color: green; font-weight: bold;"
);

This is particularly useful when you have multiple debug streams running simultaneously and need to quickly spot specific categories of logs.

Bonus: Combining Techniques

These methods are most powerful when combined. Here's a real-world debugging pattern:

async function fetchUserData(userId) {
  console.group(`fetchUserData(${userId})`);
  console.time("fetch-user");
  console.count("fetchUserData calls");

  try {
    const response = await fetch(`/api/users/${userId}`);
    console.assert(response.ok, "Failed:", response.status);
    const data = await response.json();
    console.table(data.permissions, ["resource", "access"]);
    return data;
  } finally {
    console.timeEnd("fetch-user");
    console.groupEnd();
  }
}

This gives you a clean, organized view: grouped logs, timing data, a table of permissions, and an assertion if the request fails.

Capturing Console Logs Beyond DevTools

These techniques are powerful during local development, but bugs often happen in production or on a colleague's machine where you can't open DevTools.

DevRecorder captures all console output -- including console.table(), console.trace(), console.time(), and every other method -- alongside your screen recording. Every log is synced to the video timeline, so you can see exactly what the console showed at any moment.

This means all your carefully placed debug logs are preserved in the recording, even after the browser tab is closed. Learn more about debugging frontend bugs with screen recordings.

Master Your Console, Master Your Debugging

The Console API is more powerful than most developers realize. Using the right method for the right situation makes debugging faster and your console output dramatically more readable.

And when you combine these techniques with a recording tool that captures everything, you have a complete debugging toolkit that works during development and in production.

Start debugging smarter. Try DevRecorder free to capture all your console logs alongside screen recordings, synced on a single timeline. See pricing for Pro features like extended recording duration and team sharing.

/ Ready when you are

Debug faster with DevRecorder

Record your screen with console logs and network requests. Free forever.

Add to Chrome Free← More articles