Image by Author | DALLE-3 & Canva
Many of us start our programming journey with YouTube videos, and for the sake of simplicity, they often use print()
statements to track bugs. That’s fair enough, but as beginners adopt this habit, it can become problematic. Although these statements might work for simple scripts, as your codebase expands, this approach becomes highly inefficient. Therefore, in this article, I will introduce you to Python’s built-in logging module, which solves this problem. We will see what logging is, how it differs from the print()
statements, and we will also cover a practical example to fully understand its functionality.
Why Use the Logging Module Instead of Print()?
When we talk about debugging, the Python logging module provides much more detailed information than simple print()
statements. This includes timestamps, module names, log levels, and line numbers where errors occurred, etc. These extra details help us understand the behavior of our code more effectively. The information we want to log depends on the needs of the application and the developer’s preference. So, before we proceed further, let’s discuss log levels and how to set them.
Logging Levels
You can control the amount of information you want to see using these log levels. Each log level has a numerical value that denotes its severity, with higher values indicating more severe events. For example, if you set your log level to WARNING
, you’re telling the logging module to only show you messages that are of WARNING
level or higher. This means you won’t see any DEBUG
, INFO
, or other less severe messages. This way, you can focus on the important events and ignore the noise
Here’s a table that shows the details of what each log level represents:
Log Level | Numerical Value | Purpose |
---|---|---|
DEBUG | 10 | Provides detailed information for diagnosing code-related issues, such as printing variable values and function call traces. |
INFO | 20 | Used to confirm that the program is working as expected, like displaying startup messages and progress indicators. |
WARNING | 30 | Indicates a potential problem that may not be critical to interrupt the program’s execution but could cause issues later on. |
ERROR | 40 | Represents an unexpected behavior of the code that impacts its functionality, such as exceptions, syntax errors, or out-of-memory errors. |
CRITICAL | 50 | Denotes a severe error that can lead to the termination of the program, like system crashes or fatal errors. |
Setting Up the Logging Module
To use the logging module, you need to follow some steps for configuration. This includes creating a logger, setting the logging level, creating a formatter, and defining one or more handlers. A handler basically decides where to send your log messages, such as to the console or a file. Let’s start with a simple example. We’re going to set up the logging module to do two things: first, it’ll show messages on the console, giving us useful information (at the INFO
level). Second, it’ll save more detailed messages to a file (at the DEBUG
level). I’d love it if you could follow along!
1. Setting the log level
The default level of the logger is set to WARNING
. In our case, our two handlers are set to DEBUG
and INFO
levels. Hence, to ensure all messages are managed properly, we have to set the logger’s level to the lowest level among all handlers, which, in this case, is DEBUG
.
import logging
# Create a logger
logger = logging.getLogger(__name__)
# Set logger level to DEBUG
logger.setLevel(logging.DEBUG)
2. Creating a Formatter
You can personalize your log messages using formatters. These formatters decide how your log messages will look. Here, we will set up the formatter to include the timestamp, the log level, and the message content using the command below:
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
3. Creating Handlers
As discussed previously, handlers manage where your log messages will be sent. We will create two handlers: a console handler to log messages to the console and a file handler to write log messages to a file named ‘app.log’.
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
Both handlers are then added to the logger using the addHandler()
method.
logger.addHandler(console_handler)
logger.addHandler(file_handler)
4. Testing the Logging Setup
Now that our setup is complete, let’s test if it’s working correctly before moving to the real-life example. We can log some messages as follows:
logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')
logger.critical('This is a critical message')
When you run this code, you should see the log messages printed to the console and written to a file named ‘app.log’, like this:
Console
2024-05-18 11:51:44,187 - INFO - This is an info message
2024-05-18 11:51:44,187 - WARNING - This is a warning message
2024-05-18 11:51:44,187 - ERROR - This is an error message
2024-05-18 11:51:44,187 - CRITICAL - This is a critical message
app.log
2024-05-18 11:51:44,187 - DEBUG - This is a debug message
2024-05-18 11:51:44,187 - INFO - This is an info message
2024-05-18 11:51:44,187 - WARNING - This is a warning message
2024-05-18 11:51:44,187 - ERROR - This is an error message
2024-05-18 11:51:44,187 - CRITICAL - This is a critical message
Logging User Activity in a Web Application
In this simple example, we will create a basic web application that logs user activity using Python’s logging module. This application will have two endpoints: one for logging successful login attempts and the other to document failed ones (INFO
for success and WARNING
for failures).
1. Setting Up Your Environment
Before starting, set up your virtual environment and install Flask:
python -m venv myenv
# For Mac
source myenv/bin/activate
#Install flask
pip install flask
2. Creating a Simple Flask Application
When you send a POST request to the /login endpoint with a username and password parameter, the server will check if the credentials are valid. If they are, the logger records the event using logger.info() to signify a successful login attempt. However, if the credentials are invalid, the logger records the event as a failed login attempt using logger.error().
#Making Imports
from flask import Flask, request
import logging
import os
# Initialize the Flask app
app = Flask(__name__)
# Configure logging
if not os.path.exists('logs'):
os.makedirs('logs')
log_file="logs/app.log"
logging.basicConfig(filename=log_file, level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
log = logging.getLogger(__name__)
# Define route and handler
@app.route('/login', methods=['POST'])
def login():
log.info('Received login request')
username = request.form['username']
password = request.form['password']
if username == 'admin' and password == 'password':
log.info('Login successful')
return 'Welcome, admin!'
else:
log.error('Invalid credentials')
return 'Invalid username or password', 401
if __name__ == '__main__':
app.run(debug=True)
3. Testing the Application
To test the application, run the Python script and access the /login endpoint using a web browser or a tool like curl. For example:
Test Case 01
curl -X POST -d "username=admin&password=password" http://localhost:5000/login
Output
Test Case 02
curl -X POST -d "username=admin&password=wrongpassword" http://localhost:5000/login
Output
Invalid username or password
app.log
2024-05-18 12:36:56,845 - INFO - Received login request
2024-05-18 12:36:56,846 - INFO - Login successful
2024-05-18 12:36:56,847 - INFO - 127.0.0.1 - - [18/May/2024 12:36:56] "POST /login HTTP/1.1" 200 -
2024-05-18 12:37:00,960 - INFO - Received login request
2024-05-18 12:37:00,960 - ERROR - Invalid credentials
2024-05-18 12:37:00,960 - INFO - 127.0.0.1 - - [18/May/2024 12:37:00] "POST /login HTTP/1.1" 200 -
Wrapping Up
And that wraps up this article. I strongly suggest making logging a part of your coding routine. It’s a great way to keep your code clean and make debugging easier. If you want to dive deeper, you can explore the Python logging documentation for more features and advanced techniques. And if you’re eager to enhance your Python skills further, feel free to check out some of my other articles:
Kanwal Mehreen Kanwal is a machine learning engineer and a technical writer with a profound passion for data science and the intersection of AI with medicine. She co-authored the ebook “Maximizing Productivity with ChatGPT”. As a Google Generation Scholar 2022 for APAC, she champions diversity and academic excellence. She’s also recognized as a Teradata Diversity in Tech Scholar, Mitacs Globalink Research Scholar, and Harvard WeCode Scholar. Kanwal is an ardent advocate for change, having founded FEMCodes to empower women in STEM fields.