Debugging & Logging
Reading tracebacks, print()-debugging, Python's built-in pdb debugger and breakpoint(), the logging module, log levels, and logging to files.
Bugs are inevitable — even experienced developers write code that doesn't work the first time. What separates a confident developer from a frustrated one is having good tools and habits for finding out why something went wrong. This file covers exactly that: reading error messages, using debuggers, and logging what your program does.
1. What is Debugging?
What is it?
Debugging is the process of finding and fixing errors (bugs) in your code — figuring out why the program isn't behaving the way you expect.
Definition: Debugging is the systematic process of identifying, analyzing, and fixing errors in a program.
Why do we use structured debugging techniques?
Randomly changing code and re-running it hoping something works is slow and frustrating. Structured debugging — reading error messages carefully, checking variable values step by step — finds the actual root cause far faster.
2. Reading Stack Traces (Tracebacks)
What is it?
When Python hits an unhandled error, it prints a traceback — a report showing exactly where the error occurred and the chain of function calls that led there.
Simple Example
pythondef divide(a, b): return a / b def calculate(): return divide(10, 0) calculate()
Output:
Traceback (most recent call last):
File "app.py", line 6, in <module>
calculate()
File "app.py", line 4, in calculate
return divide(10, 0)
File "app.py", line 2, in divide
return a / b
ZeroDivisionError: division by zeroExplanation of the Code
- Read a traceback from the bottom up: the last line (
ZeroDivisionError: division by zero) tells you what went wrong. - The lines above it show the call chain —
calculate()calleddivide(), and it was insidedivide()(line 2) where the actual error occurred. - This lets you trace exactly which function, and which line, caused the problem — even in a large program with many function calls.
Common Mistakes
- Panicking at a long traceback instead of reading the last line first (the actual error type and message) and then tracing upward.
- Ignoring the file names and line numbers, which point you directly to the problem location.
Important Points
- Always read the last line of a traceback first — it tells you the exception type and message.
- The call chain above it shows exactly how the program reached that point.
3. print() Debugging
What is it?
The simplest debugging technique: temporarily adding print() statements to see what values variables actually hold at different points in your code.
Simple Example
pythondef calculate_discount(price, discount_percent): print(f"DEBUG: price={price}, discount_percent={discount_percent}") discount = price * (discount_percent / 100) print(f"DEBUG: discount={discount}") final_price = price - discount return final_price print(calculate_discount(1000, 10))
Explanation
- Adding
print()statements at key points reveals exactly what values a function is working with, helping you spot exactly where a calculation goes wrong.
Common Mistakes
- Leaving debug
print()statements in production code — always remove or replace them with proper logging (see below) once done debugging. - Overusing
print()debugging for complex bugs, where a real debugger (below) would be far more efficient.
Important Points
print()debugging is quick and simple for small issues, but doesn't scale well for complex bugs or larger programs.
4. Using pdb — Python's Built-in Debugger
What is it?
pdb is Python's built-in interactive debugger — instead of guessing with print() statements, it lets you pause your program at a specific line and inspect everything happening at that exact moment.
Setting a Breakpoint
pythonimport pdb def calculate_discount(price, discount_percent): discount = price * (discount_percent / 100) pdb.set_trace() # execution pauses here final_price = price - discount return final_price print(calculate_discount(1000, 10))
When execution reaches pdb.set_trace(), it drops into an interactive prompt:
(Pdb) Useful pdb Commands
| Command | Purpose |
|---|---|
n (next) | Run the next line |
s (step) | Step into a function call |
c (continue) | Continue running until the next breakpoint |
p variable_name | Print a variable's current value |
l (list) | Show the surrounding code |
q (quit) | Exit the debugger |
Modern Alternative — breakpoint()
pythondef calculate_discount(price, discount_percent): discount = price * (discount_percent / 100) breakpoint() # same effect as pdb.set_trace(), built in since Python 3.7 final_price = price - discount return final_price
Real-World Example
Instead of adding ten different print() statements trying to find where a value became incorrect, a single breakpoint() lets you pause exactly there and inspect every variable interactively, in real time.
Common Mistakes
- Forgetting to remove
breakpoint()/pdb.set_trace()calls before deploying code — this would pause your program unexpectedly in production.
Important Points
breakpoint()(Python 3.7+) is the modern, preferred way to triggerpdb— no import needed.- Debuggers let you pause and inspect a running program, rather than guessing from static code alone.
Using Breakpoints in VS Code
Instead of typing breakpoint() in code, VS Code lets you click in the margin next to a line number to set a visual breakpoint, then run your file using the debugger (the "Run and Debug" panel) — pausing at that exact line with a full inspection panel for variables, without touching your actual code.
5. The logging Module
What is it?
While print() is fine for quick debugging, real applications use the logging module to record what's happening — with more control over severity levels, output destinations (console, file), and formatting, without cluttering actual program output.
Simple Example
pythonimport logging logging.basicConfig(level=logging.DEBUG) logging.debug("This is a debug message") logging.info("Application started") logging.warning("This is a warning") logging.error("Something went wrong") logging.critical("Critical failure!")
Output:
DEBUG:root:This is a debug message
INFO:root:Application started
WARNING:root:This is a warning
ERROR:root:Something went wrong
CRITICAL:root:Critical failure!Explanation of the Code
logging.basicConfig(level=logging.DEBUG)sets the minimum severity level that will actually be shown — here, everything (DEBUGand above) is shown.- Each
logging.xxx()call records a message at a specific severity level.
6. Log Levels
What is it?
Log levels indicate the severity or importance of a logged message, from least to most severe.
Log Level Hierarchy
| Level | Numeric Value | When to Use |
|---|---|---|
DEBUG | 10 | Detailed diagnostic info, useful only during development |
INFO | 20 | General confirmation that things are working as expected |
WARNING | 30 | Something unexpected happened, but the program still works |
ERROR | 40 | A serious problem — some functionality failed |
CRITICAL | 50 | A very serious error — the program itself may be unable to continue |
Simple Example
pythonimport logging logging.basicConfig(level=logging.WARNING) logging.debug("This won't show") # below the WARNING threshold logging.info("This won't show") # below the WARNING threshold logging.warning("This WILL show") logging.error("This WILL show")
Explanation: Setting level=logging.WARNING means only WARNING and above are actually shown — DEBUG and INFO messages are silently filtered out. This lets you control verbosity: use DEBUG level during development, and something like WARNING or ERROR in production, to avoid drowning in noise.
Common Mistakes
- Using
print()everywhere instead of proper log levels, making it impossible to filter out noisy detail in production. - Logging sensitive information (passwords, API keys) — never log secrets, even at DEBUG level.
Important Points
- Log levels let you control exactly how much detail is recorded, without deleting the actual logging code.
- Higher severity levels always include being shown when a lower threshold is set (e.g., setting
level=INFOalso showsWARNING,ERROR, andCRITICAL).
7. Logging to a File
What is it?
Instead of (or in addition to) printing to the console, logs can be written to a file — essential for tracking what happened in a program long after it finished running, especially for servers running continuously.
Simple Example
pythonimport logging logging.basicConfig( filename="app.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) logging.info("Application started") logging.warning("Low disk space") logging.error("Failed to connect to database")
File: `app.log`
2026-01-15 10:30:00,123 - INFO - Application started
2026-01-15 10:30:05,456 - WARNING - Low disk space
2026-01-15 10:30:10,789 - ERROR - Failed to connect to databaseExplanation of the Code
filename="app.log"redirects all log output to a file instead of the console.- The
formatstring customizes exactly what each log line includes — here, a timestamp (%(asctime)s), the severity level (%(levelname)s), and the actual message.
Real-World Example
Web servers and long-running applications rely heavily on log files to diagnose issues that happened hours or days earlier — since you can't attach a live debugger to a server that already crashed and restarted.
Common Mistakes
- Never rotating or limiting log file size, causing log files to grow indefinitely over time (real production systems use log rotation to manage this).
- Logging way too much at
INFOorDEBUGlevel in production, making log files huge and hard to search through.
Important Points
- Logging to files is essential for diagnosing issues in applications that run continuously, especially on servers.
- The
formatstring lets you customize exactly what information appears in each log entry.
8. Error Tracking (Brief Overview)
What is it?
In professional production systems, dedicated error-tracking tools (like Sentry or Rollbar) automatically collect, group, and alert developers about exceptions happening in a live application — going beyond simple log files.
Why This Matters
A log file sitting on a server nobody's watching isn't very useful. Error-tracking tools automatically notify the team (e.g., via email or Slack) the moment something breaks in production, along with the full traceback and context — dramatically speeding up how quickly real problems get noticed and fixed.
Important Points
- This is a conceptual introduction — actual setup of tools like Sentry is beyond this course's scope, but knowing they exist (and why) is valuable, especially for placement interviews discussing real-world software practices.
Common Beginner Mistakes — Summary for This Section
- Panicking at a traceback instead of reading the last line first.
- Leaving debug
print()statements orbreakpoint()calls in code meant for production. - Using
print()everywhere instead of theloggingmodule in real applications. - Logging sensitive data like passwords or API keys.
- Letting log files grow unbounded without any rotation strategy.
Cheat Sheet — Debugging & Logging
python# Reading tracebacks: read the LAST line first, then trace the call chain upward # pdb / breakpoint breakpoint() # pauses execution here # pdb commands: n (next), s (step), c (continue), p var (print), q (quit) # logging import logging logging.basicConfig(level=logging.INFO, filename="app.log", format="%(asctime)s - %(levelname)s - %(message)s") logging.debug("...") logging.info("...") logging.warning("...") logging.error("...") logging.critical("...")
Interview Questions
Q1. How should you read a Python traceback? Answer: Start from the last line, which shows the exception type and message. Then trace upward through the call chain to see exactly which function calls led to where the error actually occurred.
Q2. What is the difference between `print()` debugging and using `pdb`? Answer: print() debugging requires manually adding statements and re-running the program each time. pdb (or breakpoint()) pauses the program at a specific point, letting you interactively inspect any variable's current value without modifying the code repeatedly.
Q3. What are the five standard logging levels, in order of severity? Answer: DEBUG, INFO, WARNING, ERROR, CRITICAL (from least to most severe).
Q4. Why is the `logging` module preferred over `print()` in real applications? Answer: It provides severity levels (allowing filtering), can write to files, supports custom formatting (like timestamps), and can be configured differently for development versus production without changing the actual logging calls in the code.
Q5. Why should you never log sensitive information like passwords? Answer: Log files can be read by anyone with access to the server or log storage, so logging secrets creates a serious security risk.
Practice Questions
Beginner
- Write a small program that raises a
ZeroDivisionError, run it, and identify the exact line from the traceback. - Add
print()statements to debug a function that isn't calculating a total correctly. - Use
breakpoint()inside a function and step through it usingpdbcommands (n,p,c). - Configure basic logging with
level=logging.INFOand log three different messages. - Log a warning message and an error message, and observe which ones appear when the level is set to
WARNING.
Intermediate
- Set up logging to write to a file called
debug.log, including timestamps in the format. - Write a function with a deliberate bug, then use
pdbto step through it and identify the problem. - Add appropriate logging (
INFOfor normal operations,ERRORfor failures) to the Expense Tracker mini project from the Functions file. - Write a program that logs
DEBUGmessages during development but only logsWARNINGand above once you change the configured level. - Explain, using a short paragraph as a comment, the difference between a stack trace and a log file.
Challenge
- Add comprehensive logging (with appropriate levels) to the Student Management System mini project from the Database Programming file, logging every add/update/delete operation.
- Deliberately introduce three different bugs into a working program (an off-by-one error, a type mismatch, and a logic error), then use tracebacks and
pdbto find and fix each one. - Research (and briefly explain in comments) how log rotation works and why it's necessary for long-running production applications.