Ditch the Print: Elevate Your Python Code with Professional Logging

Bamgboye Oluwatosin
5 min readJun 7, 2024

--

Logging is a critical component in the lifecycle of software applications, enabling developers to understand their application’s behavior, trace issues, and gather insights. Python’s logging module offers a powerful and standard way to handle logs across different parts of an application. It supports various levels of logging, multiple output destinations, and formatting options, making it suitable for both simple scripts and complex systems.

While print statements can be useful for quick-and-dirty debugging or prototyping, they fall short in many areas where a robust logging system excels. This article outlines the advantages of using Python’s logging module over print statements for better code maintainability, debuggability, and operational excellence.

At first glance, print statements might seem sufficient for debugging purposes. They are simple, straightforward, and readily available in any programming environment. However, relying on print for logging purposes comes with significant drawbacks:

  1. No Severity Levels: Print statements lack the ability to differentiate between the types of messages they output. Whether it's a routine status update or a critical error, every message is treated the same, leading to difficulty in prioritizing and responding to issues effectively.
  2. Static Output Destinations: Print outputs messages to the console by default, making it less flexible for scenarios where logs need to be recorded to files, sent over networks, or integrated with logging infrastructure.
  3. No Built-in Formatting: With print, any additional context (such as timestamps or severity) must be manually added, which can be cumbersome and inconsistent across different parts of an application.
  4. Performance Issues: Print statements execute regardless of the context or necessity, potentially cluttering output and consuming resources inefficiently, particularly in performance-sensitive applications.
  5. Maintenance Challenges: Managing numerous print statements across a codebase becomes unwieldy, especially in larger projects. There's no centralized way to control or configure print outputs.

In contrast, Python’s logging module provides a comprehensive framework designed specifically to handle the complexities of application logging:

  1. Severity Levels and Filtering: Python’s logging system categorizes messages into levels (e.g., DEBUG, INFO, WARNING, ERROR, CRITICAL). This allows developers to filter messages based on their importance, making it easier to focus on critical issues without being overwhelmed by less significant logs.
  2. Flexible Output Destinations: Logging supports multiple output handlers, enabling logs to be directed to different destinations simultaneously — such as console, files, or even remote logging servers. This flexibility is crucial for integrating with monitoring systems and maintaining logs across diverse environments.
  3. Rich Formatting Options: The logging module offers extensive formatting capabilities, allowing logs to include contextual information like timestamps, log levels, and module names. This structured approach helps in quickly understanding when and where events occur, facilitating more effective debugging and monitoring.
  4. Performance Efficiency: Logging configurations can be set to ignore less critical messages based on the logging level. This avoids unnecessary computation and I/O, optimizing performance in production environments where resource management is crucial.
  5. Centralized Configuration: Logging configurations can be controlled from a single configuration point, enabling consistent logging behavior across the entire application. This centralization simplifies maintenance and enhances the ability to adapt logging strategies as applications evolve.

Basic Logging

To start logging in Python, you can use the basicConfig method to configure the root logger with a minimal setup.

import logging

logging.basicConfig(level=logging.INFO)

logging.info("This is an informational message.")
  • level: Sets the threshold for logging messages. Common levels include DEBUG, INFO, WARNING, ERROR, and CRITICAL.
  • If logging level is set to WARNING , logging.info() and logging.debug() doesn’t get logged because the levels are lower than the severity level set.

Log levels define the severity of the messages:

  • DEBUG: Detailed information, typically of interest only when diagnosing problems.
  • INFO: Confirmation that things are working as expected.
  • WARNING: An indication that something unexpected happened, or indicative of some problem in the near future.
  • ERROR: A more serious problem, due to which the software has not been able to perform some function.
  • CRITICAL: A very serious error, indicating that the program itself may be unable to continue running.

Logging to a File

Instead of logging to the console, you might want to log to a file.

import logging

logging.basicConfig(filename='app.log', level=logging.INFO)
logging.info("Logging to a file!")
logging.critical("Can't Log to a file!")

This configuration writes log messages to app.log instead of the standard output.

Advanced Configuration

Handlers

Handlers send the log messages to the configured output destinations. The logging module provides several handlers:

  • StreamHandler: Logs to standard output or standard error.
  • FileHandler: Logs to a file.
  • RotatingFileHandler: Logs to a file, with support for log rotation.
  • SMTPHandler: Sends logs via email.

Example of adding a file handler:

import logging

logger = logging.getLogger(__name__)
handler = logging.FileHandler('file.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("Logging with a custom handler.")

Formatters

Formatters specify the layout of log messages.

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  • %(asctime)s: Time of the event.
  • %(name)s: Logger’s name.
  • %(levelname)s: Log level.
  • %(message)s: Log message.

Why Logging Matters

  1. Debugging: Logs help in identifying the flow of execution and spotting errors.
  2. Monitoring: Continuous logging can track the health of an application.
  3. Auditing: Logs provide a record of events and transactions for auditing purposes.
  4. Analytics: Analyzing logs can yield insights into application usage and performance.

Best Practices for Logging

  1. Use Log Levels Appropriately: Log messages at the appropriate level to avoid cluttering logs with too much information or missing critical messages.
  2. Avoid Logging Sensitive Information: Be cautious about logging sensitive data like passwords or personal information.
  3. Structure Log Messages: Use structured logs (JSON or key-value pairs) for better parsing and querying.
  4. Rotate Logs: Use log rotation to manage log file sizes and avoid disk space issues.
  5. Centralize Logs: In distributed systems, centralize logs for easier access and analysis.
  6. Monitor Logs: Implement real-time monitoring and alerting based on log messages to proactively manage issues.

--

--