Appendix C: Example Configurations
This appendix provides complete, copy-paste-ready configuration examples for common debugging scenarios. Each configuration includes explanations of critical settings and common variations.
launch.json for VS Code (Python, Node, Go, Rust)
VS Code's launch.json lives in .vscode/launch.json at your project root. You can have multiple configurations and switch between them in the Run and Debug panel.
Python - Django Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Django: Run Server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver", "--noreload"],
"django": true,
"justMyCode": false,
"console": "integratedTerminal",
"env": {
"DJANGO_SETTINGS_MODULE": "myproject.settings.local",
"DEBUG": "True"
}
},
{
"name": "Django: Run Tests",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["test", "--keepdb", "--parallel=1"],
"django": true,
"justMyCode": false,
"console": "integratedTerminal"
},
{
"name": "Django: Celery Worker",
"type": "python",
"request": "launch",
"module": "celery",
"args": ["-A", "myproject", "worker", "--loglevel=info", "--pool=solo"],
"justMyCode": false,
"console": "integratedTerminal"
}
]
}
Critical settings explained:
-
"args": ["runserver", "--noreload"]: The--noreloadflag is essential. Django's auto-reloader spawns a child process, which breaks debugging. Without this flag, breakpoints won't be hit. -
"justMyCode": false: Allows stepping into Django's internal code and third-party libraries. Set totrueonly if you want to debug exclusively your own code. -
"django": true: Enables Django-specific features in the debugger, like template debugging. -
"console": "integratedTerminal": Shows output in VS Code's terminal. Alternative:"internalConsole"(Debug Console panel) or"externalTerminal"(separate window). -
"--parallel=1"in tests: Multi-process test execution breaks debugging. Force single-process mode when debugging tests. -
"--pool=solo"for Celery: Similar to Django's reload issue—Celery's default multiprocessing pool breaks debugging. Usesolopool for debugging.
Python - FastAPI Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI: Uvicorn",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": ["main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"],
"jinja": true,
"justMyCode": false,
"console": "integratedTerminal"
},
{
"name": "FastAPI: Debug Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"justMyCode": false,
"console": "integratedTerminal"
}
]
}
Notice: Unlike Django, Uvicorn's --reload works with the debugger because it uses a different reloading mechanism. However, if you encounter issues, remove --reload and restart manually.
Python - Flask Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Flask: Development Server",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development",
"FLASK_DEBUG": "0"
},
"args": ["run", "--no-debugger", "--no-reload"],
"jinja": true,
"justMyCode": false
}
]
}
Critical: "FLASK_DEBUG": "0" and --no-debugger disable Flask's built-in debugger (Werkzeug). You want VS Code's debugger, not both—running two debuggers simultaneously causes conflicts. Also note --no-reload for the same reason as Django.
Node.js - Express Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Node: Launch Express",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/app.js",
"restart": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Node: Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Node: Launch via npm",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"restart": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}
Settings explained:
-
"restart": true: Auto-reconnects debugger if the process restarts (useful with nodemon). -
"skipFiles": ["<node_internals>/**"]: Prevents stepping into Node.js core modules. Remove this array to step into Node internals if needed. -
"runtimeExecutable": "npm": Launches your app via npm script instead of directly. Useful whennpm run devdoes more than justnode app.js(e.g., sets environment variables, runs build steps).
Node.js - TypeScript Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "TypeScript: Launch",
"type": "node",
"request": "launch",
"preLaunchTask": "npm: build",
"program": "${workspaceFolder}/src/index.ts",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"smartStep": true,
"skipFiles": ["<node_internals>/**"],
"console": "integratedTerminal"
},
{
"name": "TypeScript: Attach",
"type": "node",
"request": "attach",
"port": 9229,
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true,
"skipFiles": ["<node_internals>/**"]
}
]
}
TypeScript-specific settings:
-
"preLaunchTask": "npm: build": Compiles TypeScript before debugging. Requires a tasks.json configuration (VS Code can generate this automatically). -
"outFiles": Tells the debugger where to find compiled JavaScript files. Must match yourtsconfig.jsonoutDir. -
"sourceMaps": true: Enables source map support so you debug TypeScript code, not the compiled JavaScript. -
"smartStep": true: Automatically steps through generated code (like TypeScript helpers) without stopping.
Key insight: Make sure your tsconfig.json has "sourceMap": true. Without source maps, you'll debug minified JavaScript instead of your TypeScript code.
Go - Standard Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Go: Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}",
"env": {},
"args": []
},
{
"name": "Go: Launch File",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
},
{
"name": "Go: Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "${command:pickProcess}"
},
{
"name": "Go: Launch Test",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}",
"args": ["-test.v", "-test.run", "TestMyFunction"]
}
]
}
Go-specific notes:
-
"mode": "debug": Compiles with debug symbols. Alternative:"mode": "exec"to debug a pre-built binary. -
"mode": "test": Debugs Go tests. Change-test.runto match your test name pattern. -
"processId": "${command:pickProcess}": When attaching, VS Code shows a picker to select the Go process.
Rust - Standard Application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Rust: Debug",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cargo build"
},
{
"name": "Rust: Debug Tests",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/deps/${workspaceFolderBasename}-${command:rust-analyzer.getDebugTestExecutable}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cargo test --no-run"
},
{
"name": "Rust: Debug Current File",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/${fileBasenameNoExtension}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cargo build --bin ${fileBasenameNoExtension}"
}
]
}
Rust-specific notes:
-
Requires CodeLLDB extension from VS Code marketplace.
-
"type": "lldb": Uses LLDB debugger (macOS/Linux) or MSVC debugger (Windows with appropriate config). -
"preLaunchTask": Builds the Rust project before debugging. Create this task in.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "cargo build",
"type": "shell",
"command": "cargo",
"args": ["build"],
"problemMatcher": ["$rustc"]
},
{
"label": "cargo test --no-run",
"type": "shell",
"command": "cargo",
"args": ["test", "--no-run"],
"problemMatcher": ["$rustc"]
}
]
}
Common issue: If the debugger can't find the binary, check the program path. Rust places binaries in target/debug/ (debug builds) or target/release/ (release builds). Use debug builds for debugging—they include symbols and aren't optimized.
Django Debug Toolbar settings.py Snippet
Complete configuration for Django Debug Toolbar, including common variations and gotchas.
# settings.py
# Basic setup - works for most development scenarios
INSTALLED_APPS = [
# ... your apps
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware', # Should be near the top
# ... other middleware
]
# Required: Define which IPs can see the toolbar
INTERNAL_IPS = [
'127.0.0.1',
]
# If using Docker, add the Docker gateway IP
import socket
try:
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [ip[: ip.rfind(".")] + ".1" for ip in ips]
except Exception:
pass
# Advanced configuration (optional)
DEBUG_TOOLBAR_CONFIG = {
# Automatically show toolbar (no need to click to expand)
'SHOW_TOOLBAR_CALLBACK': lambda request: DEBUG,
# Enable toolbar on all responses, not just HTML
'RENDER_PANELS': True,
# Number of SQL queries before highlighting as slow
'SQL_WARNING_THRESHOLD': 100,
# Show toolbar even with AJAX requests
'ENABLE_STACKTRACES': True,
# Results cache size (number of requests to keep)
'RESULTS_CACHE_SIZE': 100,
}
# Customize which panels are shown
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.history.HistoryPanel', # Request history
'debug_toolbar.panels.versions.VersionsPanel', # Django/Python versions
'debug_toolbar.panels.timer.TimerPanel', # Request timing
'debug_toolbar.panels.settings.SettingsPanel', # Settings
'debug_toolbar.panels.headers.HeadersPanel', # HTTP headers
'debug_toolbar.panels.request.RequestPanel', # Request details
'debug_toolbar.panels.sql.SQLPanel', # SQL queries (most useful!)
'debug_toolbar.panels.staticfiles.StaticFilesPanel', # Static files
'debug_toolbar.panels.templates.TemplatesPanel', # Template rendering
'debug_toolbar.panels.cache.CachePanel', # Cache operations
'debug_toolbar.panels.signals.SignalsPanel', # Django signals
'debug_toolbar.panels.logging.LoggingPanel', # Log messages
'debug_toolbar.panels.redirects.RedirectsPanel', # HTTP redirects
'debug_toolbar.panels.profiling.ProfilingPanel', # cProfile integration
]
Common gotchas and solutions:
Problem: Toolbar doesn't appear
Solutions:
# 1. Check INTERNAL_IPS is correct
print(f"Request from: {request.META.get('REMOTE_ADDR')}")
print(f"INTERNAL_IPS: {INTERNAL_IPS}")
# 2. Make sure middleware is early enough
# It should be AFTER SecurityMiddleware but BEFORE most others
# 3. Ensure DEBUG = True
DEBUG = True
# 4. Check your templates have </body> tag
# The toolbar injects before </body>, so it must exist
Problem: Toolbar slows development server significantly
Solution:
# Disable panels you don't use frequently
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.sql.SQLPanel', # Keep this
'debug_toolbar.panels.timer.TimerPanel', # And this
# Comment out others you don't need
]
# Or use the show/hide callback to disable for slow pages
def show_toolbar(request):
# Don't show on API endpoints
if request.path.startswith('/api/'):
return False
return DEBUG
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}
Problem: Need to debug AJAX requests
Solution:
# Enable the history panel
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.history.HistoryPanel', # Add this first
# ... other panels
]
# The history panel shows all recent requests, including AJAX
# Click any request to see its debug data
Docker-specific configuration:
# settings.py for Docker development
# Get the Docker host IP dynamically
import os
if os.environ.get('DOCKER_CONTAINER'):
import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips]
else:
INTERNAL_IPS = ['127.0.0.1']
# Alternative: Allow from any IP (DEVELOPMENT ONLY!)
# Never use this in production
def show_toolbar(request):
return DEBUG # Shows toolbar for all IPs when DEBUG=True
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}
Key insight: The SQL panel is why you install Django Debug Toolbar. It shows:
-
Every SQL query executed during the request
-
How long each query took
-
Duplicate queries (highlighting N+1 problems)
-
Query explain plans
-
Traceback showing where each query originated
This single panel solves 80% of Django performance investigations.
FastAPI Debug Configuration
FastAPI debugging requires understanding async/await contexts and Uvicorn's behavior.
VS Code launch.json for FastAPI:
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI: Uvicorn (Development)",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app",
"--reload",
"--host",
"0.0.0.0",
"--port",
"8000",
"--log-level",
"debug"
],
"jinja": true,
"justMyCode": false,
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "FastAPI: Debug Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "FastAPI: Pytest",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["-v", "-s", "--no-cov"],
"justMyCode": false,
"console": "integratedTerminal"
}
]
}
Application code example (main.py):
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import asyncio
app = FastAPI()
class Order(BaseModel):
user_id: int
items: list[str]
total: float
@app.post("/orders")
async def create_order(order: Order):
# Set breakpoint here to debug async request handling
print(f"Processing order for user {order.user_id}")
# When debugging async code, you can step into await calls
user = await fetch_user(order.user_id)
# Process order
result = await process_order(user, order.items)
return {"order_id": result.id, "status": "created"}
async def fetch_user(user_id: int):
# Simulate async database call
await asyncio.sleep(0.1)
return {"id": user_id, "name": "John Doe"}
async def process_order(user, items):
# Set breakpoints here to trace async execution
await asyncio.sleep(0.2)
return {"id": 123, "items": items}
# For debugging startup/shutdown events
@app.on_event("startup")
async def startup_event():
print("Application starting...")
# Breakpoints work here too
@app.on_event("shutdown")
async def shutdown_event():
print("Application shutting down...")
Debugging async functions - key differences:
When you set a breakpoint in an async function and step through:
-
Stepping over
await: When you hitawait fetch_user(), pressing F10 (step over) will execute the entire async function and stop at the next line. -
Stepping into
await: Pressing F11 (step into) onawait fetch_user()takes you into thefetch_userfunction, where you can debug its async execution. -
Multiple concurrent tasks: If you have multiple
asyncio.create_task()calls, breakpoints in those tasks will fire when they execute, potentially interleaving with your main execution.
Common async debugging challenges:
Problem: Breakpoint in async function never hits
Solution:
# Make sure the async function is actually being awaited
# This WON'T trigger breakpoints:
task = fetch_user(42) # Just creates a coroutine, doesn't run it
# This WILL trigger breakpoints:
result = await fetch_user(42) # Actually executes the coroutine
# Or if using create_task:
task = asyncio.create_task(fetch_user(42))
result = await task # Breakpoint hits when you await the task
Problem: Need to debug background tasks
Configuration:
from fastapi import BackgroundTasks
@app.post("/orders")
async def create_order(order: Order, background_tasks: BackgroundTasks):
# This breakpoint works
print("Creating order...")
# Add background task
background_tasks.add_task(send_confirmation_email, order.user_id)
return {"status": "created"}
async def send_confirmation_email(user_id: int):
# Breakpoints here work too!
# But they fire AFTER the response is sent
await asyncio.sleep(1)
print(f"Email sent to user {user_id}")
Profiling async FastAPI applications:
Use py-spy for async code because traditional profilers don't handle async/await well:
# Start your FastAPI app normally
uvicorn main:app --reload
# In another terminal, find the process
ps aux | grep uvicorn
# Profile it
sudo py-spy record -o profile.svg --pid <PID> --duration 60
# Generate traffic, then view profile.svg
Testing configuration (conftest.py for pytest):
import pytest
from fastapi.testclient import TestClient
from main import app
@pytest.fixture
def client():
# Synchronous test client (easier to debug)
return TestClient(app)
@pytest.fixture
async def async_client():
# Async test client (for testing async features)
from httpx import AsyncClient
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
# Use in tests:
def test_create_order(client):
# Set breakpoint here - works like normal Python debugging
response = client.post("/orders", json={
"user_id": 1,
"items": ["item1"],
"total": 100.0
})
assert response.status_code == 200
Key insight: FastAPI debugging is mostly like normal Python debugging, but with async awareness. The debugger handles async/await correctly—when you step through await calls, you're actually stepping through the async execution, not just jumping over it.
Docker Debugging Configurations
Debugging applications running in Docker containers requires special configuration because the debugger needs to communicate between the host and container.
Python/Django in Docker:
docker-compose.yml:
version: "3.8"
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000 --noreload
volumes:
- .:/code
ports:
- "8000:8000"
- "5678:5678" # Debugger port
environment:
- PYTHONUNBUFFERED=1
- DEBUG=True
stdin_open: true # Equivalent to -i
tty: true # Equivalent to -t
VS Code launch.json (using Remote-Containers):
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/code"
}
],
"justMyCode": false
}
]
}
In your Django code (for remote debugging):
# At the top of manage.py or wsgi.py
import debugpy
debugpy.listen(("0.0.0.0", 5678))
print("Waiting for debugger attach...")
debugpy.wait_for_client() # Optional: pause until debugger connects
print("Debugger attached!")
Install debugpy in requirements.txt:
debugpy==1.6.7
Workflow:
-
Start Docker container:
docker-compose up -
Container starts and prints "Waiting for debugger attach..."
-
In VS Code, press F5 and select "Python: Remote Attach"
-
Debugger connects, execution continues
-
Set breakpoints, make requests, debug normally
Node.js in Docker:
docker-compose.yml:
version: "3.8"
services:
node-app:
build: .
command: node --inspect=0.0.0.0:9229 app.js
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules # Don't override node_modules
ports:
- "3000:3000"
- "9229:9229" # Inspector port
environment:
- NODE_ENV=development
VS Code launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker: Attach to Node",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/usr/src/app",
"protocol": "inspector",
"restart": true
}
]
}
Alternative (using docker-compose):
{
"name": "Docker: Node",
"type": "node",
"request": "launch",
"runtimeExecutable": "docker-compose",
"runtimeArgs": ["up"],
"port": 9229,
"restart": true,
"console": "integratedTerminal"
}
Key points:
-
Use
--inspect=0.0.0.0not--inspect=127.0.0.1so the inspector listens on all interfaces -
Expose port 9229 in docker-compose
-
Use
pathMappingsorlocalRoot/remoteRootto map container paths to host paths
Common Docker debugging issues:
Problem: Breakpoints show as gray/unverified
Cause: Path mapping is incorrect
Solution:
// Check container path
// docker exec -it <container> pwd
// Then map correctly:
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/actual/container/path" // Use actual path from pwd
}
]
Problem: Debugger won't connect
Solutions:
# 1. Verify port is exposed
docker ps # Check PORTS column shows 5678 or 9229
# 2. Check firewall isn't blocking
# 3. Ensure debug server is listening on 0.0.0.0, not 127.0.0.1
# For Python, in container:
import debugpy
debugpy.listen(("0.0.0.0", 5678)) # Not ("127.0.0.1", 5678)
# For Node, in docker-compose command:
command: node --inspect=0.0.0.0:9229 app.js # Not --inspect=127.0.0.1
Problem: Source maps not working (TypeScript/Webpack)
Solution:
// Ensure your tsconfig.json or webpack.config.js generates source maps
// tsconfig.json:
{
"compilerOptions": {
"sourceMap": true
}
}
// Then in launch.json:
{
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
This is crucial: Docker debugging is more complex because the debugger runs on your host but the code executes in a container. Path mapping tells the debugger "when I see file /code/views.py in the container, that's actually /Users/you/project/views.py on my machine." Get this mapping wrong and breakpoints won't work.