Back to Blog
Node.jsDebuggingPerformanceDevTools

Node.js Debugging Techniques

Debug Node.js effectively. From console methods to debugger to profiling and memory analysis.

B
Bootspring Team
Engineering
August 4, 2021
6 min read

Effective debugging saves development time. Here's how to debug Node.js applications.

Console Methods#

1// Basic logging 2console.log('Value:', value); 3console.error('Error:', error); 4console.warn('Warning:', warning); 5 6// Table format 7const users = [ 8 { id: 1, name: 'John', age: 30 }, 9 { id: 2, name: 'Jane', age: 25 }, 10]; 11console.table(users); 12// ┌─────────┬────┬────────┬─────┐ 13// │ (index) │ id │ name │ age │ 14// ├─────────┼────┼────────┼─────┤ 15// │ 0 │ 1 │ 'John' │ 30 │ 16// │ 1 │ 2 │ 'Jane' │ 25 │ 17// └─────────┴────┴────────┴─────┘ 18 19// Grouping 20console.group('User Processing'); 21console.log('Step 1: Validate'); 22console.log('Step 2: Transform'); 23console.log('Step 3: Save'); 24console.groupEnd(); 25 26// Timing 27console.time('operation'); 28await someOperation(); 29console.timeEnd('operation'); 30// operation: 123.456ms 31 32// Count calls 33function processItem(item) { 34 console.count('processItem called'); 35 // ... 36} 37 38// Trace stack 39console.trace('Trace point'); 40 41// Assert 42console.assert(value > 0, 'Value must be positive'); 43 44// Clear 45console.clear(); 46 47// Dir for objects 48console.dir(complexObject, { depth: null, colors: true });

Debugger Statement#

1// Pause execution when debugger attached 2function processData(data) { 3 debugger; // Execution pauses here 4 const result = transform(data); 5 return result; 6} 7 8// Run with inspect 9// node --inspect app.js 10// node --inspect-brk app.js // Break at start 11 12// Open in Chrome DevTools 13// chrome://inspect

VS Code Debugging#

1// .vscode/launch.json 2{ 3 "version": "0.2.0", 4 "configurations": [ 5 { 6 "type": "node", 7 "request": "launch", 8 "name": "Launch Program", 9 "program": "${workspaceFolder}/src/index.ts", 10 "preLaunchTask": "tsc: build", 11 "outFiles": ["${workspaceFolder}/dist/**/*.js"], 12 "console": "integratedTerminal" 13 }, 14 { 15 "type": "node", 16 "request": "launch", 17 "name": "Debug Tests", 18 "program": "${workspaceFolder}/node_modules/jest/bin/jest", 19 "args": ["--runInBand", "--watchAll=false"], 20 "console": "integratedTerminal" 21 }, 22 { 23 "type": "node", 24 "request": "attach", 25 "name": "Attach to Process", 26 "port": 9229, 27 "restart": true 28 } 29 ] 30}
1// Conditional breakpoints 2// Right-click breakpoint in VS Code 3// Expression: user.id === '123' 4 5// Logpoints (like console.log without modifying code) 6// Right-click line number -> Add Logpoint 7// Message: "User: {user.name}, Status: {status}"

Error Stack Traces#

1// Capture stack trace 2function captureStack() { 3 const err = new Error(); 4 return err.stack; 5} 6 7// Async stack traces 8// node --async-stack-traces app.js 9 10// Custom error with stack 11class AppError extends Error { 12 constructor(message, code) { 13 super(message); 14 this.name = 'AppError'; 15 this.code = code; 16 Error.captureStackTrace(this, AppError); 17 } 18} 19 20// Clean stack traces 21function cleanStack(stack) { 22 return stack 23 .split('\n') 24 .filter(line => !line.includes('node_modules')) 25 .join('\n'); 26} 27 28// Source maps for TypeScript 29// tsconfig.json: "sourceMap": true 30// node --enable-source-maps dist/app.js

Memory Debugging#

1// Memory usage 2const used = process.memoryUsage(); 3console.log({ 4 rss: `${Math.round(used.rss / 1024 / 1024)} MB`, // Total memory 5 heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`, 6 heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`, 7 external: `${Math.round(used.external / 1024 / 1024)} MB`, 8}); 9 10// Heap snapshot 11const v8 = require('v8'); 12const fs = require('fs'); 13 14function takeHeapSnapshot() { 15 const filename = `heap-${Date.now()}.heapsnapshot`; 16 const snapshotStream = v8.writeHeapSnapshot(filename); 17 console.log(`Heap snapshot written to ${filename}`); 18} 19 20// Trigger with signal 21process.on('SIGUSR2', takeHeapSnapshot); 22 23// Heap dump on memory limit 24const MEMORY_LIMIT = 500 * 1024 * 1024; // 500MB 25 26setInterval(() => { 27 const { heapUsed } = process.memoryUsage(); 28 if (heapUsed > MEMORY_LIMIT) { 29 takeHeapSnapshot(); 30 console.error('Memory limit exceeded'); 31 } 32}, 30000); 33 34// Detect memory leaks 35// npm install memwatch-next 36const memwatch = require('memwatch-next'); 37 38memwatch.on('leak', (info) => { 39 console.error('Memory leak detected:', info); 40});

Performance Profiling#

1// CPU profiling 2const inspector = require('inspector'); 3const session = new inspector.Session(); 4session.connect(); 5 6function startProfiling() { 7 session.post('Profiler.enable'); 8 session.post('Profiler.start'); 9} 10 11function stopProfiling() { 12 session.post('Profiler.stop', (err, { profile }) => { 13 if (err) return console.error(err); 14 fs.writeFileSync( 15 `profile-${Date.now()}.cpuprofile`, 16 JSON.stringify(profile) 17 ); 18 }); 19} 20 21// Command line profiling 22// node --prof app.js 23// node --prof-process isolate-*.log > processed.txt 24 25// Clinic.js for analysis 26// npm install -g clinic 27// clinic doctor -- node app.js 28// clinic flame -- node app.js 29// clinic bubbleprof -- node app.js 30 31// perf_hooks 32const { performance, PerformanceObserver } = require('perf_hooks'); 33 34const obs = new PerformanceObserver((items) => { 35 items.getEntries().forEach((entry) => { 36 console.log(`${entry.name}: ${entry.duration}ms`); 37 }); 38}); 39obs.observe({ entryTypes: ['measure'] }); 40 41performance.mark('start'); 42await doWork(); 43performance.mark('end'); 44performance.measure('work', 'start', 'end');

Async Debugging#

1// Async hooks for tracking 2const asyncHooks = require('async_hooks'); 3 4const asyncStorage = new Map(); 5 6const hook = asyncHooks.createHook({ 7 init(asyncId, type, triggerAsyncId) { 8 asyncStorage.set(asyncId, { 9 type, 10 triggerAsyncId, 11 stack: new Error().stack, 12 }); 13 }, 14 destroy(asyncId) { 15 asyncStorage.delete(asyncId); 16 }, 17}); 18 19hook.enable(); 20 21// Track unhandled rejections 22process.on('unhandledRejection', (reason, promise) => { 23 console.error('Unhandled Rejection:', reason); 24 console.error('Promise:', promise); 25}); 26 27// Long stack traces 28// node --async-stack-traces app.js 29async function main() { 30 await step1(); 31 await step2(); 32 await step3(); // Error here shows full async trace 33}

HTTP Debugging#

1// Debug HTTP requests 2const http = require('http'); 3 4const originalRequest = http.request; 5http.request = function(options, callback) { 6 console.log('HTTP Request:', { 7 method: options.method || 'GET', 8 host: options.host || options.hostname, 9 path: options.path, 10 }); 11 return originalRequest.apply(this, arguments); 12}; 13 14// Using debug module 15// DEBUG=express:* node app.js 16const debug = require('debug')('app:http'); 17debug('Request received: %s %s', req.method, req.url); 18 19// Request timing 20app.use((req, res, next) => { 21 const start = Date.now(); 22 23 res.on('finish', () => { 24 const duration = Date.now() - start; 25 console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`); 26 }); 27 28 next(); 29});

Production Debugging#

1// Structured logging 2const pino = require('pino'); 3const logger = pino({ 4 level: process.env.LOG_LEVEL || 'info', 5 formatters: { 6 level: (label) => ({ level: label }), 7 }, 8}); 9 10logger.info({ userId: user.id, action: 'login' }, 'User logged in'); 11logger.error({ err, requestId }, 'Request failed'); 12 13// Error tracking 14const Sentry = require('@sentry/node'); 15Sentry.init({ dsn: process.env.SENTRY_DSN }); 16 17process.on('uncaughtException', (error) => { 18 Sentry.captureException(error); 19 process.exit(1); 20}); 21 22// Remote debugging 23// node --inspect=0.0.0.0:9229 app.js 24// Use SSH tunnel for production 25// ssh -L 9229:localhost:9229 user@server

Debugging Tips#

1# Run with verbose output 2NODE_DEBUG=http,net node app.js 3 4# Increase stack trace limit 5node --stack-trace-limit=100 app.js 6 7# Trace warnings 8node --trace-warnings app.js 9 10# Trace deprecations 11node --trace-deprecation app.js 12 13# Print V8 options 14node --v8-options | grep -i debug

Best Practices#

Development: ✓ Use VS Code debugger ✓ Set meaningful breakpoints ✓ Use conditional breakpoints ✓ Enable source maps Logging: ✓ Use structured logging ✓ Include context (request IDs) ✓ Log at appropriate levels ✓ Don't log sensitive data Production: ✓ Set up error tracking ✓ Use proper logging service ✓ Monitor memory and CPU ✓ Enable remote debugging carefully

Conclusion#

Effective debugging combines multiple techniques: console methods for quick checks, debuggers for stepping through code, profilers for performance issues, and proper logging for production. Master VS Code debugging, use Chrome DevTools for profiling, and set up robust error tracking for production applications.

Share this article

Help spread the word about Bootspring