Python Logging for Beginners
If you have ever used print() statements to debug your Python code, you are not alone. But there is a better way. The logging module gives you control over what messages get displayed, where they go, and how they look. It scales from simple scripts to production applications.
Why Not Just Use Print()
The print() function works for quick debugging, but it has limitations. You cannot easily turn it off in production, filter by importance, or send logs to a file. When your application grows, you need something more flexible.
Logging solves these problems. You can set different levels of importance, redirect output to files or external services, and configure formats consistently. It is built into Python standard library, so no extra installation required.
Your First Logger
Getting started with logging takes only a few lines:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
Run this script and you will see messages of different severity levels printed to the console. The default level is WARNING, so debug and info messages are hidden until you change the level.
Understanding Log Levels
Python logging has five standard levels, from least to most severe:
- DEBUG — Detailed diagnostic information
- INFO — Confirmation that things work as expected
- WARNING — Something unexpected, but the program can still run
- ERROR — A serious problem that prevented part of the program from working
- CRITICAL — A very serious error that may prevent the program from continuing
Each level has a numeric value. DEBUG is 10, INFO is 20, WARNING is 30, ERROR is 40, and CRITICAL is 50. When you set a level, you get that level and everything above it.
Configuring Output Format
The default output is functional but not pretty. You can customize it with format:
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
logger.info("Application started")
The format string uses placeholder attributes. Common ones include:
%(name)s— Logger name%(levelname)s— Log level (DEBUG, INFO, etc.)%(message)s— Your log message%(asctime)s— Timestamp%(filename)s— Source filename%(lineno)d— Line number
This makes it easier to track down where logs came from.
Logging to a File
Printing to the console is fine for development, but production applications need persistent logs. You can write logs to a file:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
filename="app.log",
filemode="a" # Append mode; use "w" to overwrite
)
logger = logging.getLogger(__name__)
logger.info("This message goes to app.log")
The filemode="a" parameter appends to the file. Change it to "w" if you want each run to start fresh.
You can also log to both console and file using multiple handlers:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# File handler
file_handler = logging.FileHandler("debug.log")
file_handler.setLevel(logging.DEBUG)
# Create formatter and add to handlers
formatter = logging.Formatter("%(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Add handlers to logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.debug("Debug message - goes to file only")
logger.info("Info message - goes to both")
This setup sends important messages to the console while keeping detailed logs in a file.
Using Loggers in Your Modules
When you have multiple modules, each should have its own logger:
# mymodule.py
import logging
logger = logging.getLogger(__name__)
def do_something():
logger.info("Doing something")
# ... rest of function
# main.py
import logging
import mymodule
logging.basicConfig(
level=logging.INFO,
format="%(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
logger.info("Starting application")
mymodule.do_something()
Using __name__ as the logger name helps you identify which module produced each log message. This is especially useful in larger applications.
Configuring with Dictionaries
For more complex setups, you can configure logging using a dictionary:
import logging
import logging.config
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "detailed",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "app.log"
}
},
"root": {
"level": "DEBUG",
"handlers": ["console", "file"]
}
}
logging.config.dictConfig(config)
logger = logging.getLogger(__name__)
logger.info("Logging configured with dictionary")
This approach works well with configuration files and makes it easy to change logging behavior without modifying code.
Best Practices
Here are some tips for using logging effectively:
Use the right level. Reserve DEBUG for detailed diagnostics during development. Use INFO for normal operations. WARNING signals something unexpected. ERROR and CRITICAL indicate problems that need attention.
Avoid logging sensitive information. Passwords, API keys, and personal data should never appear in logs.
Log exceptions with traceback information:
try:
result = 10 / 0
except Exception as e:
logger.exception("An error occurred") # Logs the exception with traceback
Use descriptive messages. Instead of “error”, write “Failed to connect to database after 3 retries”.
Keep logger names consistent. Using __name__ in each module is the standard approach.
Getting Started
The logging module is powerful enough for simple scripts and configurable enough for complex applications. Start with basicConfig for quick needs, then move to handlers and formatters as your requirements grow.
Replace your print() debugging statements with proper logging calls. Your future self will thank you when debugging production issues.
See Also
- logging-module — Full reference for the logging module
- sys-module — System-specific parameters and functions
- os-module — Operating system interface for file operations