Skip to main content
  1. Posts/

Debug Smarter: Logging vs. Print in Python

·5 mins
devops security python coding tips & tricks debugging
Table of Contents

The Problem with Python Print Debugging
#

If you’ve written in Python for at least 2 minutes, you have almost certainly used the print() function. It’s a simple and effective way to get output to the console - like adding visual indicators on the status of a script and debugging problems in Python code.

Up until recently, I’ve generally done debugging of Python code with print() statements. Run into a problem and need to debug? Add print(someVar) and get the output in the console. Fix the problem? Go back and comment it out. Rinse and repeat…

Therein lies a major downside with using print() for debugging. It’s great for one-off, quick and dirty checks. However, at some point, you end up with print() statements littered throughout your code and it becomes impractical to comment/uncomment each one individually when you need to debug.

A friend recently showed me how he used the Python logging module for debugging in a script. When I saw how simple it is to use, I couldn’t believe I hadn’t seen it used before. Let’s take a look at how it works…

An Example of Print Debugging
#

While I suspect the idea of using print() for debugging is obvious, I don’t want to make assumptions. Before we jump into using logging, consider the following example…

def add_numbers(a, b):
    # Print statements to debug function inputs
    print(f'Input a = {a}')
    print(f'Input b = {b}')
    result = a+b
    # Print statement to debug result
    print(f'result var = {result}')
    return result

def main():
    answer = add_numbers(10, 5)
    print(f'Answer: {answer}')

main()

If we run the code, the output looks like this…

Input a = 10
Input b = 5
result var = 15
Answer: 15

In this basic example, print() statements are used to provide debug info for the add_numbers() function. When we want to disable (or enable) those debug messages, we have to comment out (or uncomment) each one individually. Now, let’s look at how the Python logging module can make this easier.

Better Debugging with Python Logging
#

Logging Configuration
#

The logging module is part of the Python standard library. To use it, simply import it with import logging.

There are 5 different logger methods (read levels) that can be used - CRITICAL, ERROR, WARNING, INFO, or DEBUG (listed from least to most verbose). Without any configuration, the logging module implicitly defaults to the WARNING logger method/level. This can be explicitly configured with the logging.basicConfig() function.

After setting the logging level, simply replace print() with logging.<level>() where <level> indicates the desired logging level where that specific message will be displayed. The logging level for each individual statement can (and likely should) be different depending on what you want displayed by default vs. when you are, as an example, debugging code.

Practical Use
#

Taking the same example from earlier, let’s replace the print() statements with their logging counterparts…

import logging

# Explicitly set the logging level 
logging.basicConfig(level=logging.DEBUG)

def add_numbers(a, b):
    # Logging statements to debug function inputs
    logging.debug(f'Input a = {a}')
    logging.debug(f'Input b = {b}')
    result = a+b
    # Logging statement to debug calculation
    logging.debug(f'result var = {result}')
    return result

def main():
    answer = add_numbers(10, 5)
    print(f'Answer: {answer}')

main()

The output now closely resembles the original print statements.

DEBUG:root:Input a = 10
DEBUG:root:Input b = 5
DEBUG:root:result var = 15
Answer: 15

Awesome! But, as we alluded to earlier, what if we want to output some statements all the time and only display other statements when debugging? We can easily do this by changing the logging level for the desired statement(s).

Referencing our example code, let’s say we want to output the result variable at the WARNING level but only want to output the a and b variables at the DEBUG level. It would look like this (note the change to the basicConfig() function from DEBUG to WARNING)…

import logging

# Explicitly set the logging level 
logging.basicConfig(level=logging.WARNING)

def add_numbers(a, b):
    # Logging statements to debug function inputs
    logging.debug(f'Input a = {a}')
    logging.debug(f'Input b = {b}')
    result = a+b
    # Logging statement to debug calculation
    logging.warning(f'result var = {result}')
    return result

def main():
    answer = add_numbers(10, 5)
    print(f'Answer: {answer}')

main()

With this change, the output becomes…

WARNING:root:result var = 15
Answer: 15

And now if we want to debug, we simply set the basicConfig() logging level to DEBUG and our output would look like this…

DEBUG:root:Input a = 10
DEBUG:root:Input b = 5
WARNING:root:result var = 15
Answer: 15

Lastly, if we want to hide all logging statement outputs, we do so by setting the logging level to a higher threshold than the logging output you want to suppress. For example, setting the logging.basicConfig() level to ERROR would suppress any logging messages with a level of DEBUG, INFO, or WARNING.

In our example code, setting the logging level to ERROR only returns the print statement in the main function.

Answer: 15

A Logging “Caveat” to Consider
#

When writing Python code and using the logging module, it is important to consider the impact that the logging level you choose for individual statements will have. The logging documentation states…

The key benefit of having the logging API provided by a standard library module is that all Python modules can participate in logging, so your application log can include your own messages integrated with messages from third-party modules.

The important part here is noting that other Python modules will also output logging info to the console as well. This isn’t particularly bad, but it is worth noting as some of your logging output may get lost in a sea of other logging output…specifically if you choose to use the DEBUG logging level.

Final Thoughts
#

For a developer, incorporating the Python logging module may be an obvious choice. For those of us who aren’t developers, this is likely an underutilized module even though it’s built into the standard library.

Now, import logging is one of the first lines I write in any new Python project. It’s a simple addition that makes debugging code faster, cleaner, and much more maintainable.

Helpful Links #

Josh Jaggard
Author
Josh Jaggard