Everything You Need to Know About Python Logging Levels
Logging is a critical tool for debugging, monitoring, and auditing applications. Unlike simple print() statements, Python's built-in logging module allows you to categorize messages by severity. This categorization is done using Logging Levels, which are numerical constants that determine which messages are recorded and which are ignored.
Understanding and correctly configuring logging levels is essential for running a robust production system.
1. The Five Standard Logging Levels
Python defines five standard severity levels, listed here from the lowest (least severe) to the highest (most severe).
| Level Constant | Numerical Value | Purpose and Use Case |
|---|---|---|
| DEBUG | 10 | Detailed information, typically of interest only when diagnosing problems. Example: Variable values, step-by-step function entry/exit. |
| INFO | 20 | Confirmation that things are working as expected. Example: Successful configuration load, service started/stopped. |
| WARNING | 30 | An indication that something unexpected happened, or indicative of a potential problem in the near future. The software is still working. Example: Deprecated API usage, resource limits approaching. |
| ERROR | 40 | Due to a serious problem, the software has not been able to perform some function. Example: Failed database connection, file not found (when required). |
| CRITICAL | 50 | A very serious error, indicating that the program itself may be unable to continue running. Example: Catastrophic resource failure, major process crash. |
2. How Logging Levels Control Output
The logging system uses two key settings to control which messages make it to the output:
- Logger Level (The Filter): This is set on the
logging.getLogger()instance. It acts as the minimum severity threshold for a message to even be considered. - Handler Level (The Destination Filter): This is set on the output destination (e.g., file, console, socket). It acts as a second filter before the message is finally recorded.
Rule: A log message is only processed if its severity level is greater than or equal to both the Logger Level and the Handler Level.
Code Example: Setting the Logger Level
If you set the logger level to INFO, messages below INFO (i.e., DEBUG) will be ignored.
import logging
# 1. Configure the root logger to handle messages INFO and above
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("MyApp")
# 2. Log messages
logger.debug("This message is level 10.") # Ignored (10 < 20)
logger.info("Service started successfully.") # Processed (20 >= 20)
logger.error("Failed to connect to DB.") # Processed (40 >= 20)
# Output:
# INFO:MyApp:Service started successfully.
# ERROR:MyApp:Failed to connect to DB.
3. Practical Usage Scenarios
Expert developers adjust the logging level dynamically based on the environment to control verbosity and performance.
| Environment | Recommended Logger Level | Rationale |
|---|---|---|
| Local Development | DEBUG (10) | Maximum verbosity is required to trace variable states and execution flow. |
| Testing/Staging | INFO (20) | Confirm application flow and integration points without excessive detail. |
| Production | WARNING (30) or ERROR (40) | Focus on problems requiring immediate attention. Reduces I/O overhead from excessive logging. |
Expert Technique: Multiple Handlers
In complex applications, you can use multiple handlers to route messages to different destinations based on severity:
import logging
import sys
# Create a logger instance
logger = logging.getLogger('my_prod_app')
logger.setLevel(logging.DEBUG) # Logger allows ALL messages (DEBUG and up)
# 1. Console Handler (Only show INFO/WARNING/ERROR)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
logger.addHandler(console_handler)
# 2. File Handler (Send ALL messages, including DEBUG, to a file for deep analysis)
file_handler = logging.FileHandler('debug.log')
file_handler.setLevel(logging.DEBUG)
logger.addHandler(file_handler)
# Messages are logged
logger.debug("Trace function call.") # ✅ Goes to File ONLY
logger.error("Critical component fail.") # ✅ Goes to Console AND File
4. Advanced Concepts and Best Practices
- Custom Levels: While not recommended for general use, you can define custom levels using
logging.addLevelName()if the five standard levels are insufficient for domain-specific tracking (e.g.,AUDIT = 25). - The
NOTSETLevel (0): This is the default level for loggers. If a logger is set toNOTSET, it delegates the severity decision to its parent logger. If the root logger isNOTSET, no messages will be processed unlessbasicConfig()is used to set a level. - Performance: Logging is an I/O operation (writing to disk/network) and is orders of magnitude slower than CPU operations. If a loop runs a million times, having
DEBUGlogs inside it can be a performance killer, even if they are ultimately filtered out by the handler. Use thelogger.isEnabledFor(level)check in performance-critical areas if logging is expensive.
# Performance check example
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Expensive log message: %s", calculate_large_string())
