Are you struggling with logging messages that look like they were written by a robot? Do your logs make you feel like you’re reading a technical manual instead of understanding what’s going on in your code?
First things first, the logging module. This is the go-to option for most Python developers, and it’s well maintained by a huge community that will always have an answer to your doubts. The logging module comes with six different levels of messages: DEBUG, INFO, WARNING, ERROR, CRITICAL, and NOTSET (which we won’t be covering in this guide). Each level is designed for a specific purpose, so choose wisely!
Now formatting your log messages. The logging module provides three different ways to format your logs: %-formatting, string.Template, and str.format(). Let’s dig into this at each one of them.
%-Formatting is the original way of formatting log messages in Python. It uses placeholders (%) followed by conversion specifiers (%d for date/time, %n to insert a newline character, etc.) to format your logs. Here’s an example:
# Import the logging module
import logging
# Import the datetime module
from datetime import datetime
# Create a logger object with the name of the current module
logger = logging.getLogger(__name__)
# Create a formatter object with the desired format for log messages
formatter = '%(asctime)s %(levelname)s %(message)s'
# Create a handler object to handle the log messages
handler = logging.StreamHandler()
# Set the formatter for the handler
handler.setFormatter(logging.Formatter(formatter))
# Add the handler to the logger
logger.addHandler(handler)
# Log a debug message using the logger
logger.debug('This is a debug message')
# The logging module allows for easy creation and management of log messages
# The datetime module is used to get the current date and time for the log message
# The logger object is created with the name of the current module to easily identify where the log message is coming from
# The formatter object specifies the format for the log message, including the date, log level, and message
# The handler object is responsible for handling the log messages, in this case, printing them to the console
# The setFormatter() method sets the formatter for the handler
# The addHandler() method adds the handler to the logger, allowing it to handle log messages
# The debug() method is used to log a debug message, which is a low-level message used for debugging purposes
In this example, we create a logger object and set up the formatting string for our logs using %-formatting. We then add a StreamHandler to print our logs in real time. When you run this code, your console will look something like this:
# Create a logger object
logger = logging.getLogger()
# Set up formatting string for logs using %-formatting
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
# Add a StreamHandler to print logs in real time
stream_handler = logging.StreamHandler()
# Set the formatter for the StreamHandler
stream_handler.setFormatter(formatter)
# Add the StreamHandler to the logger object
logger.addHandler(stream_handler)
# Log a debug message with the current timestamp
logger.debug('This is a debug message')
String.Template provides an alternative way to format log messages using string templates. It’s similar to %-formatting but with some added benefits like support for conditional statements and loops. Here’s an example:
# Import the necessary modules
import logging # Import the logging module to enable logging functionality
from datetime import datetime # Import the datetime module to access current time
from string import Template # Import the Template class from the string module to enable string templating
# Create a logger object
logger = logging.getLogger(__name__) # Create a logger object with the name of the current module
# Define the string template for formatting log messages
template_string = r"""
{time} {levelname} {message}
"""
# Create a Template object using the template string
formatter = Template(template_string) # Create a Template object using the template string defined above
# Create a StreamHandler object to handle logging output
handler = logging.StreamHandler() # Create a StreamHandler object to handle logging output
# Set the formatter for the handler using the Template object
handler.setFormatter(logging.Formatter(str(formatter))) # Set the formatter for the handler using the Template object created above
# Add the handler to the logger
logger.addHandler(handler) # Add the handler to the logger object created above
# Log a debug message
logger.debug('This is a debug message') # Log a debug message using the logger object created above
In this example, we create a logger object and set up the formatting string for our logs using string templates. We then add a StreamHandler to print our logs in real time. When you run this code, your console will look something like this:
# Create a logger object
logger = logging.getLogger()
# Set up formatting string for logs using string templates
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
# Add a StreamHandler to print logs in real time
stream_handler = logging.StreamHandler()
# Set the formatter for the StreamHandler
stream_handler.setFormatter(formatter)
# Add the StreamHandler to the logger object
logger.addHandler(stream_handler)
# Log a debug message with current timestamp
logger.debug("This is a debug message")
str.format() provides an even more flexible way to format log messages using the Python formatting syntax. It’s similar to %-formatting but with added support for string interpolation and dictionary mapping. Here’s an example:
# Import the necessary libraries
import logging # Import the logging library to enable logging functionality
from datetime import datetime # Import the datetime library to enable date and time functionality
# Create a logger object
logger = logging.getLogger(__name__) # Create a logger object with the name of the current module
# Create a handler object
handler = logging.StreamHandler() # Create a handler object to handle the logging output
# Create a formatter function
formatter = lambda record: f"{datetime.now():%Y-%m-%d %H:%M:%S} {record.levelname} {record.getMessage()}" # Create a formatter function to format the log messages with the current date and time, log level, and message
# Set the formatter for the handler
handler.setFormatter(logging.Formatter(formatter)) # Set the formatter for the handler to use the formatter function created above
# Add the handler to the logger
logger.addHandler(handler) # Add the handler to the logger to handle the logging output
# Log a debug message
logger.debug('This is a debug message') # Log a debug message using the logger object created above
In this example, we create a logger object and set up the formatting string for our logs using str.format(). We then add a StreamHandler to print our logs in real time. When you run this code, your console will look something like this:
# Create a logger object
logger = logging.getLogger()
# Set up formatting string for logs using str.format()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
# Add StreamHandler to print logs in real time
stream_handler = logging.StreamHandler()
# Set the formatter for the StreamHandler
stream_handler.setFormatter(formatter)
# Add the StreamHandler to the logger object
logger.addHandler(stream_handler)
# Log a debug message with the current date and time
logger.debug('This is a debug message')
Now timestamps when logging. Timestamps are critical features of logs that print functions don’t have. In addition to knowing where a problem appeared, it’s important to know when it happened. Make sure to use the standard format to write timestamps, i.e., the ISO-8601 format.