Appendices
Appendix A: Tool Installation Quick Start
This appendix provides step-by-step installation instructions for the essential tracing tools discussed throughout this chapter. Each section includes installation commands, basic configuration, and verification steps to confirm the tool is working correctly.
Django Debug Toolbar (Python)
The pain point: You're staring at a Django view that takes 3 seconds to load, and you have no idea which of the 47 database queries is the culprit. Or you need to understand which middleware components are processing your request. Reading code hasn't helped—you need visibility into what's actually happening.
Installation (takes 5 minutes):
# Install the package
pip install django-debug-toolbar
Configuration in your Django project's settings.py:
# Add to INSTALLED_APPS
INSTALLED_APPS = [
# ... other apps
'debug_toolbar',
]
# Add to MIDDLEWARE (important: place early in the list)
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ... other middleware
]
# Configure internal IPs (crucial for the toolbar to appear)
INTERNAL_IPS = [
'127.0.0.1',
]
# If using Docker, you need to get the Docker host IP
import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [ip[: ip.rfind(".")] + ".1" for ip in ips]
Add URL patterns in your project's urls.py:
from django.urls import include, path
urlpatterns = [
# ... your patterns
path('__debug__/', include('debug_toolbar.urls')),
]
Verification: Start your Django development server and load any page. You should see a sidebar on the right side of the page with panels for SQL queries, templates, cache, signals, and more. If you don't see it, check that:
-
You're accessing the site from an IP in
INTERNAL_IPS -
The middleware is installed correctly
-
Your templates include the closing
</body>tag (the toolbar injects before it)
Common gotcha: If the toolbar doesn't appear and you're using Docker, the issue is almost always INTERNAL_IPS. The Docker container sees requests from the Docker gateway, not 127.0.0.1. Use the dynamic IP detection code shown above.
Flask-DebugToolbar (Python)
The pain point: Your Flask application is behaving mysteriously—routes are firing in unexpected orders, or your database queries are multiplying. Flask's built-in debugger shows you exceptions, but you need execution visibility.
Installation:
pip install flask-debugtoolbar
Configuration in your Flask application:
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
# Required: set a secret key
app.config['SECRET_KEY'] = 'your-secret-key-here'
# Optional: configure toolbar behavior
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False # Don't intercept redirects
app.config['DEBUG_TB_PROFILER_ENABLED'] = True # Enable profiler panel
# Initialize the toolbar
toolbar = DebugToolbarExtension(app)
Verification: Run your Flask app in debug mode (flask run or app.run(debug=True)). Load any page—you should see a toolbar on the right edge. Click it to expand panels showing request details, SQL queries, template rendering, and more.
Important: Unlike Django Debug Toolbar, Flask-DebugToolbar shows by default when app.debug = True. For production safety, ensure debug mode is disabled in production environments.
React DevTools (Chrome/Firefox)
The pain point: Your React component is re-rendering constantly, killing performance. Or you're passing props through six layers of components and losing track of what data reaches where. You need to see the component tree and data flow as they actually exist at runtime.
Installation:
Chrome:
-
Visit the Chrome Web Store
-
Search for "React Developer Tools"
-
Click "Add to Chrome"
-
Look for the React icon in your browser toolbar (it lights up on React pages)
Firefox:
-
Visit Firefox Add-ons
-
Search for "React Developer Tools"
-
Click "Add to Firefox"
Alternative: For React Native, install the standalone app:
npm install -g react-devtools
Then connect it to your app by adding to your code:
import "react-devtools";
Verification: Open any React application and open your browser's developer tools (F12 or Cmd+Option+I on Mac). You should see two new tabs: "Components" and "Profiler".
-
Components tab: Shows the React component tree. Click any component to inspect its props, state, and hooks.
-
Profiler tab: Records rendering performance. Click the record button, interact with your app, then stop recording to see which components rendered and how long each took.
Key insight: The React icon in your browser toolbar has three states:
-
Red: React detected, development build (good for debugging)
-
Blue: React detected, production build (optimized, harder to debug)
-
Gray: No React detected on this page
If you see gray on a React page, the page may be using React in an unconventional way or the DevTools failed to detect it.
Vue DevTools (Chrome/Firefox)
The pain point: You're debugging Vue's reactivity system—a data change isn't triggering the expected component update. Or you need to trace events bubbling through your component hierarchy. Vue's reactivity is powerful but opaque without the right tools.
Installation:
Chrome:
-
Chrome Web Store → Search "Vue.js devtools"
-
Add to Chrome
-
The Vue icon appears in the toolbar
Firefox:
-
Firefox Add-ons → Search "Vue.js devtools"
-
Add to Firefox
For Vue 3 specifically: Make sure you install the updated version that supports Vue 3. The legacy version only works with Vue 2.
Standalone app (for Electron, Safari, or custom setups):
npm install -g @vue/devtools
vue-devtools
Verification: Open a Vue application. Open DevTools—you should see a "Vue" tab. This tab shows:
-
Components tree: Inspect any component's data, computed properties, and props
-
Events: See all events emitted, when, and with what data
-
Vuex (if used): Time-travel through state mutations
-
Performance: Identify slow component renders
Common gotcha: Vue DevTools only works in development builds. If your app is built for production (NODE_ENV=production), the devtools won't connect. Always debug with development builds.
Router tracing: If using Vue Router, the DevTools show route navigation history—incredibly useful for debugging complex routing behaviors.
VS Code Debugger Configuration (All Languages)
The pain point: You're bouncing between console.log statements, restarting your app dozens of times, and still can't figure out why the variable is undefined at line 47. Debuggers let you pause execution, inspect everything, and step through code—but only if you configure them properly.
VS Code's debugger is powerful but requires a launch.json configuration file. This file lives in .vscode/launch.json in your project root.
Creating the configuration:
-
Open VS Code in your project
-
Click the Run and Debug icon in the sidebar (or press Cmd+Shift+D)
-
Click "create a launch.json file"
-
Select your environment (Python, Node.js, etc.)
Python Configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false // CRITICAL: allows stepping into library code
},
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver", "--noreload"], // --noreload is crucial
"django": true,
"justMyCode": false
},
{
"name": "Python: FastAPI",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": ["main:app", "--reload"],
"justMyCode": false
}
]
}
Critical setting: "justMyCode": false is essential for tracing execution through unfamiliar codebases. With this set to true (the default), the debugger won't step into third-party library code—exactly what you're trying to understand. Always set this to false when tracing execution.
Node.js / JavaScript Configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Node: Current File",
"type": "node",
"request": "launch",
"program": "${file}",
"skipFiles": [] // Don't skip any files
},
{
"name": "Node: Express App",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/app.js",
"restart": true,
"console": "integratedTerminal"
},
{
"name": "Chrome: Attach",
"type": "chrome",
"request": "attach",
"port": 9222,
"webRoot": "${workspaceFolder}"
}
]
}
TypeScript Configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "TypeScript: Current File",
"type": "node",
"request": "launch",
"program": "${file}",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true // Essential for TypeScript debugging
}
]
}
Go Configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Go: Launch File",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
},
{
"name": "Go: Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}"
}
]
}
Rust Configuration (requires CodeLLDB extension):
{
"version": "0.2.0",
"configurations": [
{
"name": "Rust: Debug",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}
Verification:
-
Set a breakpoint by clicking left of a line number (a red dot appears)
-
Select your configuration from the dropdown in the Run panel
-
Press F5 or click the green play button
-
Your program should start and pause at the breakpoint
-
You should see local variables in the sidebar and can step through code with F10/F11
Common issues:
-
Breakpoint appears gray/hollow: The code hasn't loaded yet. For web servers, the breakpoint won't be hit until you make a request.
-
Debugger won't start: Check your
programpath is correct and the file exists. -
Can't step into library code: Verify
justMyCode: false(Python) orskipFiles: [](Node.js).
py-spy Installation and Usage
The pain point: Your Python application is slow in production, but you can't use a debugger there. Or you have a long-running process and want to profile it without restarting or modifying code. You need zero-overhead profiling of live processes.
py-spy is a sampling profiler that attaches to running Python processes without requiring any code changes or restarts. It works by reading Python's internal state from outside the process, meaning negligible performance impact.
Installation:
# macOS
brew install py-spy
# Linux (most distros)
pip install py-spy
# Or download from releases
curl -L https://github.com/benfred/py-spy/releases/latest/download/py-spy-x86_64-unknown-linux-musl.tar.gz | tar xz
Basic Usage:
Profile a running process (requires PID):
# Find your Python process
ps aux | grep python
# Attach py-spy to it (may require sudo)
sudo py-spy top --pid 12345
This shows a live, top-like view of which functions are consuming CPU time.
Record a flame graph:
# Profile for 30 seconds and generate a flame graph
sudo py-spy record -o profile.svg --pid 12345 --duration 30
# Open profile.svg in a browser to see the flame graph
Flame graphs visualize where your program spends time. The width of each bar represents how much time was spent in that function. Click any bar to zoom in.
Profile a script from startup:
# Instead of: python manage.py runserver
py-spy record -o profile.svg -- python manage.py runserver
Profile only specific threads:
# Useful for Django workers or Celery tasks
sudo py-spy record -o profile.svg --pid 12345 --subprocesses
Key insight: Unlike cProfile, which instruments every function call (slowing execution), py-spy samples the call stack periodically (typically 100 times per second). This means:
-
Negligible overhead: <1% performance impact
-
Safe for production: Can attach to live processes
-
No code changes: Works on any Python program
-
Limited precision: Won't catch very fast functions
When to use py-spy:
-
Profiling production systems safely
-
Understanding hot paths in long-running processes
-
Quick performance checks without instrumentation
-
Debugging processes you can't restart
When NOT to use py-spy:
-
You need exact call counts (use
cProfile) -
You need line-by-line timing (use
line_profiler) -
You're debugging logic, not performance (use a debugger)
Common gotcha: On some systems, you need sudo or CAP_SYS_PTRACE capability to attach to processes. If you get "permission denied", either run with sudo or add the capability:
sudo setcap cap_sys_ptrace+ep $(which py-spy)
Node.js Inspector Setup
The pain point: You're debugging a Node.js application—maybe Express, maybe a Nest.js backend—and console.log isn't cutting it. You need breakpoints, call stack inspection, and variable watching. Node's inspector protocol provides this, but you need to enable it correctly.
Built-in Node.js inspector (Node 8+):
Start Node with the --inspect flag:
# Start with inspector on default port 9229
node --inspect app.js
# Specify a different port
node --inspect=9230 app.js
# Break immediately on first line (useful for startup debugging)
node --inspect-brk app.js
You'll see output like:
Debugger listening on ws://127.0.0.1:9229/abc-123-def
For help, see: https://nodejs.org/en/docs/inspector
Connecting Chrome DevTools:
-
Open Chrome
-
Navigate to
chrome://inspect -
Click "Open dedicated DevTools for Node"
-
Your Node process should appear under "Remote Target"
-
Click "inspect" next to your process
Now you have full Chrome DevTools connected to your Node process—breakpoints, profiler, memory tools, everything.
VS Code integration (recommended for most workflows):
Add to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Node: Attach",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"]
}
]
}
Then:
-
Start your app with
node --inspect app.js -
In VS Code, press F5 and select "Node: Attach"
-
Set breakpoints directly in VS Code
Debugging npm scripts:
If you normally run npm start, modify package.json:
{
"scripts": {
"start": "node app.js",
"debug": "node --inspect app.js",
"debug-brk": "node --inspect-brk app.js"
}
}
Then run npm run debug instead of npm start.
TypeScript considerations:
For TypeScript projects, ensure source maps are enabled in tsconfig.json:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist"
}
}
Then debug the compiled JavaScript with source maps:
node --inspect dist/app.js
The debugger will automatically map back to your TypeScript source files.
Debugging with nodemon (auto-restart on changes):
# Install nodemon if needed
npm install -g nodemon
# Run with inspector
nodemon --inspect app.js
Configure VS Code to reconnect automatically:
{
"type": "node",
"request": "attach",
"name": "Attach to nodemon",
"restart": true,
"port": 9229
}
Now when nodemon restarts your app, the debugger reconnects automatically.
Security warning: --inspect binds to 127.0.0.1 by default, which is safe. Never use --inspect=0.0.0.0 in production or on shared networks—this exposes your debugger to anyone on the network, allowing arbitrary code execution.
Production alternative: For production debugging, use:
node --inspect-port=9229 --inspect=127.0.0.1 app.js
Then tunnel to it securely via SSH:
ssh -L 9229:localhost:9229 user@production-server
This lets you debug production safely through an encrypted tunnel.