Debugging with Print Statements and Logging
One of the simplest ways to debug a program is to insert print statements at strategic points in the code to display the values of variables or just to mark how far the execution has reached. This technique is often jokingly called “print debugging” or “caveman debugging”.
It can be very effective for small programs or simple issues – you add:
print("Got here")
print("variable x:", x)
to verify the state of the program at that moment.
Example – using print for debugging
def add_numbers(a, b):
print(f"DEBUG: a={a}, b={b}") # debug print
result = a + b
print(f"DEBUG: result={result}") # debug print
return result
total = add_numbers(5, 7)
print("Total is", total)
When run, the extra print statements will show the internal values and help ensure the function is working as expected.
You would remove or comment out these debug prints once the issue is resolved.
Drawbacks of using print for debugging
-
You might forget to remove them, leading to cluttered output in production.
-
They cannot be easily turned on or off without editing the code.
-
They all write to standard output in the same way, with no notion of severity or source.
-
In a large program, it’s hard to filter out what’s important.
A Better Way – Using the logging Module
For more systematic debugging output, Python provides the built-in logging module.
Logging allows you to record messages with different severity levels (DEBUG, INFO, WARNING, ERROR, CRITICAL) and flexibly control what gets output and where.
Unlike print, logging can:
-
be configured to write to files,
-
show timestamps,
-
be globally turned on/off,
-
be filtered by level.
Example – using logging for debugging
import logging
logging.basicConfig(level=logging.DEBUG)
# Logging is configured to show all messages at DEBUG level and above.
def add_numbers(a, b):
logging.debug(f"Entering add_numbers: a={a}, b={b}")
result = a + b
logging.debug(f"Computed result: {result}")
return result
logging.info("Starting calculation...")
total = add_numbers(5, 7)
logging.info(f"Total is {total}")
logging.warning("This is just a demo of logging.")
Explanation
-
logging.basicConfig(level=logging.DEBUG)sets up logging to display messages of level DEBUG or higher. -
logging.debug()logs fine-grained details. -
logging.info()logs general informational messages. -
logging.warning()logs a warning-level message.
Sample Output
INFO:root:Starting calculation...
DEBUG:root:Entering add_numbers: a=5, b=7
DEBUG:root:Computed result: 12
INFO:root:Total is 12
WARNING:root:This is just a demo of logging.
Notice that log messages are prefixed with the level and logger name (root by default).
Advantages of logging over print
| Logging (logging module) | Print (print function) |
|---|---|
| Designed for both debugging and production; can remain in code. | Primarily for quick debugging; usually removed for production. |
| Supports log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL). Can filter by level. | No concept of level or filtering. |
| Flexible output: console, files, network, etc. Can include timestamps, module info. | Prints only to standard output unless redirected manually. |
| Consistent formatting for all messages. | Manual formatting required for each print. |
| Can be toggled/configured without code changes. | Must remove or comment out print statements manually. |
In Summary
-
Use print statements for quick, small-scale debugging or experiments.
-
Use the logging module for larger applications, persistent debugging, and production monitoring.
-
Logging is more powerful, configurable, and considered a best practice.
Tip: If you have leftover print statements, convert them to appropriate logging calls so they can stay in your code without cluttering output.
