🏠

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:

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:

  1. Visit the Chrome Web Store

  2. Search for "React Developer Tools"

  3. Click "Add to Chrome"

  4. Look for the React icon in your browser toolbar (it lights up on React pages)

Firefox:

  1. Visit Firefox Add-ons

  2. Search for "React Developer Tools"

  3. 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".

Key insight: The React icon in your browser toolbar has three states:

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:

  1. Chrome Web Store → Search "Vue.js devtools"

  2. Add to Chrome

  3. The Vue icon appears in the toolbar

Firefox:

  1. Firefox Add-ons → Search "Vue.js devtools"

  2. 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:

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:

  1. Open VS Code in your project

  2. Click the Run and Debug icon in the sidebar (or press Cmd+Shift+D)

  3. Click "create a launch.json file"

  4. 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:

  1. Set a breakpoint by clicking left of a line number (a red dot appears)

  2. Select your configuration from the dropdown in the Run panel

  3. Press F5 or click the green play button

  4. Your program should start and pause at the breakpoint

  5. You should see local variables in the sidebar and can step through code with F10/F11

Common issues:

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:

When to use py-spy:

When NOT to use py-spy:

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:

  1. Open Chrome

  2. Navigate to chrome://inspect

  3. Click "Open dedicated DevTools for Node"

  4. Your Node process should appear under "Remote Target"

  5. 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:

  1. Start your app with node --inspect app.js

  2. In VS Code, press F5 and select "Node: Attach"

  3. 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.