Debugging Tools for Developers: Beyond Console.log
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.
- Click the record button (or press
Ctrl+Shift+Eto start recording and reload) - Interact with the page
- 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:
- Take a heap snapshot (baseline)
- Perform the action that leaks memory (open/close a modal, navigate between routes)
- Force garbage collection (click the trash can icon)
- Take another heap snapshot
- 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
source-map: Separate.mapfiles. Best for production -- only downloaded when DevTools is open.eval-source-map: Embedded in eval statements. Fast rebuilds, good for development.hidden-source-map: Generates.mapfiles without referencing them in the bundle. Upload to your error tracking service (Sentry, Datadog, Bugsnag) -- your users never see the source, but stack traces are readable.
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
- Stop using console.log as your primary debugging tool. VS Code's debugger with conditional breakpoints is faster for any non-trivial bug.
- Set up source maps in production from day one. Hidden source maps with Sentry integration costs minutes to configure and saves hours of debugging.
- Install React DevTools or Vue DevTools if you use either framework. The profiler alone prevents shipping unnecessary re-renders.
- Use structured logging (pino, winston) in any backend that reaches production. You will need to search your logs eventually.
- Learn the Performance and Memory panels. They find the actual cause -- not guessing and adding memoization everywhere.
- Use logpoints instead of console.log -- they never accidentally end up in a commit. Set up error tracking before you need it -- the worst time to configure Sentry is during an outage.