Debugging and Logging

Chapter Outline

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.

python
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:

bash
> /.../pdb_demo.py(7)debug_demo()
-> return a / b
(Pdb)

Code stepping using PDB

  • n – next line
  • s – step into function
  • p var – print variable
  • q – 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

python
import logging
logging.basicConfig(level=logging.INFO)
logging.info("App started")

Output:

INFO:root:App started

Log Levels and Usage

LevelUse Case Example
DEBUGInternal state for developers
INFOSuccessful operations
WARNINGRecoverable problems or deprecations
ERRORSerious problems that need attention
CRITICALApplication is unusable

Changing Output Format

python
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)

Output:

bash
2023-07-17 16:21:10,200 - INFO - App started

Writing Logs to a File

python
logging.basicConfig(
filename='app.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)

Logging in Functions

python
def divide(a, b):
if b == 0:
logging.error("Attempted to divide by zero")
return None
return a / b
divide(10, 0)

Logging Exceptions

python
try:
1 / 0
except ZeroDivisionError:
logging.exception("Something went wrong!")

Output:

bash
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
1import logging
2
3logging.basicConfig(
4 filename='calculator.log',
5 level=logging.INFO,
6 format='%(asctime)s - %(levelname)s - %(message)s'
7)
8
9def add(a, b): return a + b
10def subtract(a, b): return a - b
11def multiply(a, b): return a * b
12
13def divide(a, b):
14 if b == 0:
15 logging.error("Division by zero attempted.")
16 raise ValueError("Cannot divide by zero.")
17 return a / b
18
19def main():
20 print("CLI Calculator (With Logging and Exception Handling)")
21 try:
22 operation = input("Choose operation (add/subtract/multiply/divide): ").strip().lower()
23 num1 = float(input("Enter first number: "))
24 num2 = float(input("Enter second number: "))
25
26 if operation == "add":
27 result = add(num1, num2)
28 elif operation == "subtract":
29 result = subtract(num1, num2)
30 elif operation == "multiply":
31 result = multiply(num1, num2)
32 elif operation == "divide":
33 result = divide(num1, num2)
34 else:
35 logging.warning(f"Invalid operation: {operation}")
36 return
37
38 logging.info(f"{operation}({num1}, {num2}) = {result}")
39 print(f"Result: {result}")
40
41 except ValueError as ve:
42 logging.error(f"Value error: {ve}")
43 print(f"Value error: {ve}")
44 except Exception as e:
45 logging.critical(f"Unexpected error: {e}", exc_info=True)
46 print("An unexpected error occurred.")
47
48if __name__ == "__main__":
49 main()

Check your understanding

Test your knowledge of Debugging and Logging in Python

Feedback