Chapter 6: Debugging and Logging
Debugging is an essential part of software development. Almost every piece of software that is written has errors or various kinds. It is the programmer's job to identify and fix pieces of code that make some software malfunction. This process is called debugging.
Most programming languages offer a debugger
that allow the programmer to set breakpoints, to halt program execution at certain points within the program, and inspect the current state of the program variables. This allows the programmer to identify whether some variables may have incorrect values at certain point of the execution.
The debugger also allows the programmer to step though the code line by line, or step over, or out of certain sections of the code to ensure that the code execution order is correct. The Python Debugger is the pdb
.
6.1 Debugging with pdb
and IDE Tools
The Python Debugger, or pdb
lets you pause program execution and step through code interactively.
import pdb
def debug_demo():
a = 10
b = 0
pdb.set_trace()
return a / b
debug_demo()
The program pauses after the set_trace()
function is executed:
> /.../pdb_demo.py(7)debug_demo()
-> return a / b
(Pdb)
Code stepping using PDB
n
– next lines
– step into functionp var
– print variableq
– quit debugger
IDE Debugging
Most modern IDEs (e.g., PyCharm, VS Code) natively support:
- Breakpoints
- Step over/into
- Variable watching
- Call stack inspection
Instead of running the python program you need to debug the program.
6.2 Logging in Python
Python’s built-in logging
module provides a flexible framework for emitting log messages from your code. It's far superior to using print()
statements for:
- Debugging
- Tracking errors
- Auditing system events
- Application monitoring in production
Basic Logging Example
import logging
logging.basicConfig(level=logging.INFO)
logging.info("App started")
Output:
INFO:root:App started
Log Levels and Usage
Level | Use Case Example |
---|---|
DEBUG |
Internal state for developers |
INFO |
Successful operations |
WARNING |
Recoverable problems or deprecations |
ERROR |
Serious problems that need attention |
CRITICAL |
Application is unusable |
Changing Output Format
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
Output:
2023-07-17 16:21:10,200 - INFO - App started
Writing Logs to a File
logging.basicConfig(
filename='app.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
Logging in Functions
def divide(a, b):
if b == 0:
logging.error("Attempted to divide by zero")
return None
return a / b
divide(10, 0)
Logging Exceptions
try:
1 / 0
except ZeroDivisionError:
logging.exception("Something went wrong!")
Output:
2023-07-17 01:26:40,389 - ERROR - Something went wrong!
Traceback (most recent call last):
File "/.../basic_logging.py", line 11, in <module>
1 / 0
~~^~~
ZeroDivisionError: division by zero
6.3 Refactoring the Calculator: Fault-Tolerant Version
Here’s a refactored CLI calculator with logging and error handling.
calculator.py
import logging
logging.basicConfig(
filename='calculator.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def add(a, b): return a + b
def subtract(a, b): return a - b
def multiply(a, b): return a * b
def divide(a, b):
if b == 0:
logging.error("Division by zero attempted.")
raise ValueError("Cannot divide by zero.")
return a / b
def main():
print("CLI Calculator (With Logging and Exception Handling)")
try:
operation = input("Choose operation (add/subtract/multiply/divide): ").strip().lower()
num1 = float(input("Enter first number: "))
num2 = float(input("Enter second number: "))
if operation == "add":
result = add(num1, num2)
elif operation == "subtract":
result = subtract(num1, num2)
elif operation == "multiply":
result = multiply(num1, num2)
elif operation == "divide":
result = divide(num1, num2)
else:
logging.warning(f"Invalid operation: {operation}")
return
logging.info(f"{operation}({num1}, {num2}) = {result}")
print(f"Result: {result}")
except ValueError as ve:
logging.error(f"Value error: {ve}")
print(f"Value error: {ve}")
except Exception as e:
logging.critical(f"Unexpected error: {e}", exc_info=True)
print("An unexpected error occurred.")
if __name__ == "__main__":
main()