← All articles
TESTING Debugging Tools for Developers: Beyond Console.log 2026-02-09 · 7 min read · debugging · devtools · chrome

Debugging Tools for Developers: Beyond Console.log

Testing 2026-02-09 · 7 min read debugging devtools chrome nodejs vscode profiling

Debugging Tools for Developers: Beyond Console.log

Console.log is not a debugging strategy. It's a coping mechanism. You scatter print statements across your code, squint at the terminal output, add more print statements, and eventually stumble onto the problem. There are better tools -- they've existed for years, but most developers never learn to use them.

This guide covers the debugging tools that actually save time -- Chrome DevTools features you're probably ignoring, Node.js inspection, VS Code debugger configurations, and production debugging strategies.

Chrome DevTools: The Features You're Not Using

You probably use the Elements and Console tabs. But the real power of Chrome DevTools is in the Network, Performance, and Memory panels.

Network Panel

The Network panel shows every request your page makes. Filter by type (XHR, JS, CSS, Img) to cut through the noise. The Waterfall column shows timing -- use it to find requests that block rendering.

Right-click any request and select "Copy as cURL" to reproduce it in the terminal. You get the exact request with headers, cookies, and body, ready to paste into your terminal or share with a backend developer.

Two underused features: Throttling (set to "Slow 3G" to expose broken loading states) and Block request URL (right-click a request to simulate failures and test error handling).

Performance Panel

The Performance panel records a timeline of everything the browser does -- scripting, rendering, painting, garbage collection.

  1. Click the record button (or press Ctrl+Shift+E to start recording and reload)
  2. Interact with the page
  3. Stop recording

The flame chart shows you exactly which functions are eating CPU time. Long yellow bars are JavaScript execution. Long purple bars are layout/rendering. If you see forced reflows (layout thrashing), your DOM reads and writes are interleaved when they shouldn't be.

Look for "Long Tasks" -- any task over 50ms blocks the main thread and makes the UI feel sluggish. The Performance panel marks them with a red triangle.

Memory Panel

Memory leaks in web apps are common and hard to find without tooling. The workflow for finding one:

  1. Take a heap snapshot (baseline)
  2. Perform the action that leaks memory (open/close a modal, navigate between routes)
  3. Force garbage collection (click the trash can icon)
  4. Take another heap snapshot
  5. Compare the two -- look at "Objects allocated between Snapshot 1 and Snapshot 2"

Detached DOM nodes are the most common leak. Event listeners and closures that reference removed DOM elements prevent garbage collection.

Console Beyond console.log

The console API has methods most developers never touch:

// Grouped output for complex debugging
console.group("User authentication flow");
console.log("Token retrieved:", token);
console.log("User decoded:", user);
console.groupEnd();

// Table display for arrays and objects
console.table(users);

// Timing
console.time("API call");
await fetch("/api/data");
console.timeEnd("API call"); // "API call: 243ms"

// Conditional logging
console.assert(user.role === "admin", "Expected admin role", user);

// Stack trace without throwing
console.trace("How did we get here?");

$0 in the console refers to the currently selected element in the Elements panel. $_ is the result of the last evaluated expression. copy() copies any value to the clipboard -- copy($0.outerHTML) grabs the selected element's HTML.

Node.js Debugging with --inspect

Running node --inspect starts the V8 inspector protocol. You can connect Chrome DevTools, VS Code, or any compatible debugger.

# Start with inspector
node --inspect server.js

# Break on the first line of execution
node --inspect-brk server.js

# Specify a custom port
node --inspect=0.0.0.0:9230 server.js

After starting with --inspect, open chrome://inspect in Chrome, and your Node process appears under "Remote Target." Click "inspect" to get a full debugging session with breakpoints, call stack, and variable inspection. For Bun, the equivalent flags are bun --inspect and bun --inspect-brk.

ndb: A Better Node Debugger

ndb (by the Chrome DevTools team) launches a dedicated Chromium instance pre-configured for your Node process:

npx ndb server.js
npx ndb npm test

It handles the connection automatically, shows your project files in Sources, and supports child processes.

VS Code Debugger

VS Code's built-in debugger is more powerful than most developers realize. It supports breakpoints, conditional breakpoints, logpoints, and step-through debugging with a graphical UI.

launch.json Configurations

Create .vscode/launch.json in your project. Here are configurations that cover common setups:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Node.js (TypeScript)",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/src/index.ts",
      "runtimeArgs": ["--loader", "ts-node/esm"],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    },
    {
      "name": "Attach to Running Process",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "name": "Debug Jest Tests",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "${relativeFile}"],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "name": "Chrome: Debug Frontend",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}/src",
      "sourceMaps": true
    }
  ]
}

The skipFiles setting is important. Without it, stepping through code drops you into Node internals and node_modules -- a miserable experience. Always skip internal files and dependencies.

Conditional Breakpoints

Right-click a breakpoint in VS Code and select "Edit Breakpoint." You can set a condition:

user.id === "abc-123"

The breakpoint only triggers when the condition is true. This is transformative when debugging loops or request handlers that fire thousands of times but you only care about one specific case.

Hit count breakpoints trigger after the breakpoint has been hit N times. Set it to 100 to skip the first 99 iterations of a loop.

Logpoints are breakpoints that log a message instead of stopping execution. Right-click the gutter and choose "Add Logpoint." Enter a message with expressions in curly braces:

User {user.name} has role {user.role}

Logpoints are superior to console.log because you don't modify your source code. You can add and remove them without restarting the process or leaving debugging artifacts in your commits.

Source Maps

Source maps connect your compiled/bundled/minified code back to the original source. Without them, debugging production code means reading mangled variable names and compressed single-line files.

Most bundlers generate source maps automatically:

// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: true, // generates .map files
  },
});
// webpack.config.js
module.exports = {
  devtool: "source-map", // full source maps for production
  // devtool: "eval-source-map"  // faster but less accurate, for development
};

Source Map Types

Framework-Specific DevTools

React DevTools

Install the React Developer Tools browser extension. It adds two panels to Chrome DevTools:

Components -- Shows the React component tree with props and state for each component. Click any component to inspect its current props, state, and hooks.

Profiler -- Records which components re-rendered and why. Start a recording, interact with your app, stop, and examine the flame graph. Components that re-rendered are colored (yellow/orange for slow, blue for fast). Enable "Highlight updates when components render" in settings -- if the entire page flashes when you type a single character, you have a re-rendering problem.

// Use React.memo to prevent unnecessary re-renders
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => {
  return items.map((item) => <ListItem key={item.id} item={item} />);
});

// Use useMemo for expensive computations
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

Vue DevTools

The Vue.js devtools extension provides a component inspector, Vuex/Pinia state viewer, event timeline, and router inspector. It also ships as a standalone Electron app (@vue/devtools) for non-browser environments.

Debugging in Production

In production, you can't attach a debugger. You need a different toolkit.

Error Tracking

An error tracking service (Sentry, Datadog, Bugsnag) captures unhandled exceptions with stack traces, user context, and breadcrumbs. This is non-negotiable for any production application.

// Sentry setup (most popular option)
import * as Sentry from "@sentry/node";

Sentry.init({
  dsn: "https://[email protected]/0",
  tracesSampleRate: 0.1, // sample 10% of transactions for performance
  environment: process.env.NODE_ENV,
});

Upload source maps during your build so stack traces point to original source files:

npx @sentry/cli sourcemaps upload --release=1.0.0 ./dist

Structured Logging

Console.log doesn't scale. In production, use structured logging -- JSON-formatted log lines with consistent fields.

import pino from "pino";

const logger = pino({ level: process.env.LOG_LEVEL || "info" });

logger.info({ userId: user.id, orderId: order.id }, "Order placed");
logger.error({ err, requestId: req.id }, "Payment processing failed");

Structured logs are searchable. When something breaks, you query userId:"abc-123" AND level:"error" instead of grepping through gigabytes of unstructured text.

Feature Flags for Debugging

Feature flags aren't just for gradual rollouts. Ship debug-level logging behind a flag and enable it for specific users when investigating an issue:

if (featureFlags.isEnabled("verbose-auth-logging", { userId: user.id })) {
  logger.debug({ token, claims, session }, "Auth flow details");
}

This gets you detailed debugging information from production without deploying new code or drowning in logs.

Recommendations