🏠

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:

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:

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:

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:

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:

{
  "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:

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:

  1. Stepping over await: When you hit await fetch_user(), pressing F10 (step over) will execute the entire async function and stop at the next line.

  2. Stepping into await: Pressing F11 (step into) on await fetch_user() takes you into the fetch_user function, where you can debug its async execution.

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

  1. Start Docker container: docker-compose up

  2. Container starts and prints "Waiting for debugger attach..."

  3. In VS Code, press F5 and select "Python: Remote Attach"

  4. Debugger connects, execution continues

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

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.