Python’s standard logging library uses a numeric severity system to filter log messages.
When you set a log level on a logger, you define a threshold: the logger will discard any message with a severity below this threshold and only process messages at or above it.
1. The 5 Standard Log Levels
| Level | Numeric Value | When to Use It | Example Scenario |
|---|---|---|---|
| CRITICAL | 50 | Severe errors that halt a core process or crash the app. Requires immediate intervention. | Out of memory, database connection lost, disk full. |
| ERROR | 40 | Serious problems where the app failed to perform a specific function but can continue running. | External API request timed out, file write failed. |
| WARNING | 30 | Default Level. Indicates something unexpected happened, or a potential problem is imminent. | Low disk space, deprecated library usage, bad login attempt. |
| INFO | 20 | General operational events confirming that things are working as expected. | "Successfully connected to database", "Kafka consumer started". |
| DEBUG | 10 | Detailed diagnostic information, useful during local development and troubleshooting. | "Query returned 4 rows", "Received payload: {...}". |
| NOTSET | 0 | Default for non-root loggers. Inherits the level from its parent logger. | Directs child loggers to rely on parent configuration. |
2. How Level Filtering Works
When a log message is generated, Python compares its numeric value against the active logger's threshold:
Message Level (e.g., INFO = 20) >= Logger Threshold (e.g., WARNING = 30)?
20 >= 30 ---> FALSE (Log is silently discarded)
40 >= 30 ---> TRUE (Log is processed and printed)
3. Self-Contained Code Example
import logging
# Configure root logger to output INFO (20) and above
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# This will NOT print (DEBUG 10 < INFO 20)
logger.debug("Parsing payload dictionary...")
# This WILL print (INFO 20 >= INFO 20)
logger.info("Successfully connected to the database.")
# This WILL print (ERROR 40 >= INFO 20)
logger.error("Failed to fetch profiles from downstream service.")
4. The Hierarchy Gotcha: Level Inheritance
By default, the Root logger is initialized to WARNING (30).
Non-root (named) loggers inherit their level from their closest ancestor that has an explicit level set.
The Silent Log Trap:
import logging
# 1. No global config is set (Root defaults to WARNING)
logger = logging.getLogger("app.database")
# 2. You try to log an INFO message
logger.info("Connecting...") # NOTHING PRINTS!
5. Best Practice: Dynamic Environment Configuration
In containerized microservices (e.g., Docker, Kubernetes), you should never hardcode log levels.
Instead, read the level dynamically from an environment variable with a safe default.
import os
import logging
# 1. Fetch level from env variable, default to 'INFO' if not set
env_level = os.getenv("LOG_LEVEL", "INFO").upper()
# 2. Convert string level to numeric value safely
numeric_level = getattr(logging, env_level, logging.INFO)
# 3. Configure the Root baseline
logging.basicConfig(
level=numeric_level,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("app.service")
logger.info(f"Logger initialized with level: {env_level}")
6. Environment Recommendations
Development (DEBUG):
SetLOG_LEVEL=DEBUG. Helps trace raw incoming database queries, inspect message payloads, and track API states.-
Production (INFO or WARNING):
SetLOG_LEVEL=INFO. High-frequency DEBUG logging writes massive amounts of data to stdout, which can:- Exhaust server disk space
- Overwhelm aggregation engines (Elasticsearch, Grafana Loki, Splunk)
- Degrade application I/O performance
Top comments (0)