Python Logging: A Practical Guide to Best Practices


As applications grow in complexity, the need for robust logging becomes critical. Good logs aren't just for squashing bugs; they provide invaluable insights into your application's performance and behavior. Python's built-in logging module is a powerful and flexible tool designed for this exact purpose.

This guide will walk you through the essentials of Python logging, from basic configuration to best practices that will make your applications easier to debug and monitor. 


Why Use the logging Module Instead of print()?

Many developers start by using print() statements to see what's happening in their code. While this works for simple scripts, it quickly becomes unmanageable in larger applications.

Here's why logging is superior:

  • No Timestamps: print() doesn't tell you when an event occurred without manually adding datetime logic, which clutters your code.

  • Lack of Control: You can't easily filter messages. A log file full of miscellaneous print() statements from different development stages is nearly impossible to parse.

  • Inflexible Output: With print(), you're mostly stuck sending messages to the console. The logging module can route messages to files, network sockets, or other destinations with ease.


Understanding Logging Levels

The logging module allows you to categorize messages by severity using levels. When you configure a logger, you set a minimum level, and any message with a lower severity will be ignored. This lets you control the verbosity of your logs in different environments (e.g., show detailed DEBUG messages in development but only WARNING and above in production).

The standard levels are:

  • DEBUG (10): Detailed information, typically of interest only when diagnosing problems.

  • INFO (20): Confirmation that things are working as expected.

  • WARNING (30): An indication that something unexpected happened, or a potential problem in the near future (e.g., 'disk space low'). The software is still working as expected.

  • ERROR (40): Due to a more serious problem, the software has not been able to perform some function.

  • CRITICAL (50): A serious error, indicating that the program itself may be unable to continue running.

By default, the logging level is set to WARNING.


How to Configure Python Logging

You can get started with logging without installing anything. The simplest way to configure logging is with the basicConfig() method. It should be called as early as possible in your application's startup process.

Basic Configuration: Logging to a File

Let's set up a basic logger that writes messages of level INFO and higher to a file named app.log.

Python
import logging

# Configure the logger
logging.basicConfig(
    level=logging.INFO,
    filename='app.log',
    filemode='w', # 'w' for write (overwrite), 'a' for append
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logging.debug("This is a debug message. It won't be logged.")
logging.info("Application starting up.")
logging.warning("User password is weak.")
logging.error("Failed to connect to the database.")
logging.critical("Catastrophic failure! Shutting down.")

If you check the app.log file, you will see:

2025-07-19 10:56:03,456 - root - INFO - Application starting up.
2025-07-19 10:56:03,456 - root - WARNING - User password is weak.
2025-07-19 10:56:03,457 - root - ERROR - Failed to connect to the database.
2025-07-19 10:56:03,457 - root - CRITICAL - Catastrophic failure! Shutting down.

Notice the debug message was ignored because we set the level to INFO. The format string adds valuable context like the timestamp, logger name, level, and the message itself.


Logging Best Practices 

For applications composed of multiple modules, relying solely on basicConfig() isn't enough. Follow these best practices for a scalable and maintainable logging strategy.

1. Use Module-Level Loggers

Instead of logging directly through the root logger, create a dedicated logger for each module. This gives you granular control over the logging behavior of different parts of your application. The standard convention is to use the module's name.

Python
# in my_module.py
import logging

# Create a logger specific to this module
logger = logging.getLogger(__name__)

def do_something():
    logger.info("Doing something interesting in my_module.")

2. Configure Once at the Application Entry Point

Your main application script is responsible for configuring the logging system. This ensures that log messages from all modules are handled consistently.

Python
# in main.py
import logging
import my_module

# Configure logging for the entire application
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logging.info("Main application has started.")
my_module.do_something()

3. Log Exceptions Correctly

When an exception occurs, you need to know what happened and where. The logger.exception() method is perfect for this. When called from within an except block, it logs a message at the ERROR level and automatically includes the full exception traceback.

Python
import logging

logger = logging.getLogger(__name__)

try:
    result = 1 / 0
except ZeroDivisionError:
    logger.exception("An error occurred during division.")

This is far more informative than just logging the error message string.


Conclusion

The Python logging module is an essential tool for any serious developer. By moving beyond print() and embracing structured logging, you gain critical visibility into your application's behavior.

Key takeaways:

  • Always use logging over print() for applications.

  • Understand logging levels to control log verbosity.

  • Use basicConfig() for simple scripts but adopt a more robust configuration for larger projects.

  • Follow best practices: create module-level loggers with logging.getLogger(__name__) and handle exceptions with logger.exception().

Investing a small amount of time to set up proper logging will save you countless hours of debugging down the road. Happy coding!

Comments

Popular Posts