Module 7.2

Debugging and Logging

Debugging is detective work - find bugs, understand them, fix them. Every programmer spends significant time debugging. The difference between a good programmer and a great one is often the ability to systematically track down bugs. Master these techniques to become an effective bug hunter who solves problems quickly and confidently.

45 min
Intermediate
Hands-on
What You'll Learn
  • Systematic debugging workflow
  • Print debugging techniques
  • Using pdb breakpoints
  • Python logging module
  • Reading stack traces
Contents
01

The Debugging Workflow

Debugging is not random trial and error. It is a systematic process of reproducing a bug, locating its source, understanding why it happens, fixing it, and verifying the fix works. Following a structured approach saves time and frustration.

Key Concept

Bugs Are Clues, Not Mysteries

Every bug leaves a trail. Error messages, unexpected outputs, and program behavior all provide clues. Your job is to follow these clues methodically until you find the root cause.

Why it matters: Random changes waste time and can introduce new bugs. Systematic debugging finds the actual cause and fixes it properly.

Debugging Workflow
4 Steps
1
REPRODUCE

Create minimal test case that triggers the bug

2
LOCATE

Find exact line where bug occurs

3
FIX

Change only what needs changing

4
VERIFY

Test bug is gone AND no new bugs

Critical!
Repeat until fixed

Reading Stack Traces

When Python raises an exception, it prints a stack trace showing the sequence of function calls that led to the error. Read it from bottom to top.

# Example stack trace
Traceback (most recent call last):
  File "main.py", line 10, in 
    result = calculate(data)
  File "main.py", line 6, in calculate
    return process(value)
  File "main.py", line 3, in process
    return 10 / value  # ERROR IS HERE
ZeroDivisionError: division by zero

The bottom shows the actual error. Each line above shows where that function was called from. Read bottom-up to trace the execution path.

Types of Bugs

Syntax Error

Invalid Python code

Read the error message carefully
Compile-time
Runtime Error

Crashes during execution

Use stack trace to find location
Crashes
Logic Error

Wrong output, no crash

Add prints or use debugger
Hardest!
Edge Case Bug

Fails on unusual inputs

Test boundary conditions
Sneaky
03

The pdb Debugger

Python's built-in debugger, pdb, lets you pause execution, inspect variables, and step through code line by line. It is more powerful than print debugging but requires learning a few commands.

Setting Breakpoints

# Method 1: Insert breakpoint in code
def calculate(x, y):
    result = x + y
    breakpoint()  # Execution pauses here (Python 3.7+)
    return result * 2

# Method 2: Using pdb directly
import pdb
pdb.set_trace()  # Older method, still works

When Python hits a breakpoint, it opens an interactive prompt. You can inspect variables, run expressions, and control execution.

Essential pdb Commands

(Pdb)
Debugger Commands Cheatsheet
n
next Execute current line, go to next
s
step Step into function call
c
continue Run until next breakpoint
p
print expr Print value of expression
l
list Show source code around current line
q
quit Exit debugger
w
where Show stack trace

Debugging Session Example

# Example debugging session
(Pdb) p x        # Print variable x
42
(Pdb) p y        # Print variable y
0
(Pdb) p x / y    # Evaluate expression - AHA! Division by zero
*** ZeroDivisionError: division by zero
(Pdb) n          # Go to next line
(Pdb) c          # Continue execution

Use p to evaluate any Python expression. This helps you understand program state and find the root cause of bugs.

Practice: Using pdb

Task: Add a breakpoint before the return statement to inspect the result.

def greet(name):
    greeting = f"Hello, {name}!"
    return greeting
Show Solution
def greet(name):
    greeting = f"Hello, {name}!"
    breakpoint()  # Inspect greeting here
    return greeting

Task: Add a conditional breakpoint that only triggers when i equals 5.

def find_index(items, target):
    for i, item in enumerate(items):
        if item == target:
            return i
    return -1
Show Solution
def find_index(items, target):
    for i, item in enumerate(items):
        if i == 5:
            breakpoint()  # Only break at index 5
        if item == target:
            return i
    return -1

Task: Add breakpoints to trace the recursive binary search. When would you use 'step' vs 'next'?

def binary_search(arr, target, lo, hi):
    if lo > hi:
        return -1
    mid = (lo + hi) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search(arr, target, mid+1, hi)
    else:
        return binary_search(arr, target, lo, mid-1)
Show Solution
def binary_search(arr, target, lo, hi):
    breakpoint()  # Inspect lo, hi, mid each call
    if lo > hi:
        return -1
    mid = (lo + hi) // 2
    # Use 'p arr[mid]' and 'p target' to compare
    # Use 's' (step) to enter recursive call
    # Use 'n' (next) to skip to next line
    if arr[mid] == target:
        return mid
    # ... rest of function
04

The Logging Module

Unlike print statements, logging can be left in production code. You can control which messages appear based on severity levels and output to files, not just the console. Professional applications use logging instead of print.

Logging Levels

import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug("Detailed info for debugging")
logging.info("General information")
logging.warning("Something unexpected happened")
logging.error("An error occurred")
logging.critical("System is about to crash!")

Set the level to control what appears. DEBUG shows everything, WARNING shows only warnings and above. This lets you leave debug logs in code but hide them in production.

Configuring Log Format

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='app.log'  # Write to file instead of console
)

logging.info("Application started")
logging.error("Database connection failed")

Include timestamps, levels, and file names in log format. Write to files to keep a permanent record of what happened.

Practice: Logging

Task: Set up logging at INFO level. Log "Starting process" at start and "Process complete" at end.

Show Solution
import logging

logging.basicConfig(level=logging.INFO)

def process():
    logging.info("Starting process")
    # ... do work ...
    logging.info("Process complete")

process()

Task: Write a function that logs errors with full exception details using logging.exception().

Show Solution
import logging

logging.basicConfig(level=logging.DEBUG)

def divide(a, b):
    try:
        return a / b
    except Exception:
        logging.exception("Division failed")
        return None

divide(10, 0)  # Logs full traceback

Task: Create a custom logger named "myapp" with a file handler that logs to "myapp.log".

Show Solution
import logging

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

handler = logging.FileHandler("myapp.log")
handler.setLevel(logging.DEBUG)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
handler.setFormatter(fmt)

logger.addHandler(handler)
logger.info("Custom logger ready!")

Key Takeaways

Follow the Workflow

Reproduce, locate, fix, verify. Do not skip steps. Random changes create new bugs.

Print Debugging Works

Simple and effective. Use f-strings with = for quick variable inspection.

Learn pdb Basics

breakpoint(), n, s, c, p are enough for most debugging. Use for complex bugs.

Use Logging in Production

Unlike prints, logs can stay in code. Control verbosity with levels.

Read Stack Traces

Read from bottom to top. The last line shows the error, lines above show the path.

Create Minimal Test Cases

Strip away unrelated code to isolate the bug. Smaller code is easier to debug.

Knowledge Check

Quick Quiz

Test what you've learned about Python debugging and logging

1 What does breakpoint() do in Python 3.7+?
2 Which pdb command steps into a function call?
3 Which logging level shows only warnings and errors?
4 What does f"{x=}" print if x is 42?
5 What is the correct order to read a stack trace?
6 Which logging level is the most verbose?
Answer all questions to check your score