Skip to main content

Everything You Need to Know About Python Logging Levels

· 7 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

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 ConstantNumerical ValuePurpose and Use Case
DEBUG10Detailed information, typically of interest only when diagnosing problems. Example: Variable values, step-by-step function entry/exit.
INFO20Confirmation that things are working as expected. Example: Successful configuration load, service started/stopped.
WARNING30An 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.
ERROR40Due to a serious problem, the software has not been able to perform some function. Example: Failed database connection, file not found (when required).
CRITICAL50A 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:

  1. 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.
  2. 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.

EnvironmentRecommended Logger LevelRationale
Local DevelopmentDEBUG (10)Maximum verbosity is required to trace variable states and execution flow.
Testing/StagingINFO (20)Confirm application flow and integration points without excessive detail.
ProductionWARNING (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 NOTSET Level (0): This is the default level for loggers. If a logger is set to NOTSET, it delegates the severity decision to its parent logger. If the root logger is NOTSET, no messages will be processed unless basicConfig() 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 DEBUG logs inside it can be a performance killer, even if they are ultimately filtered out by the handler. Use the logger.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())

Sources and Further Reading

  1. Python Documentation - logging Levels
  2. Python Documentation - Basic Logging Tutorial
  3. Python Documentation - Handlers and Formatters
  4. Real Python - Python Logging Guide