Python Logging
The logging module is Python's built-in solution for recording program events. Replacing print() with logging gives you control over what gets recorded, where it goes, and how it's formatted โ without changing your code.
Why Use logging Instead of print()
print() goes to stdout and has no built-in way to filter by severity, route to files, or disable in production. The logging module provides all of this:
Log Levels
Python defines five standard log levels, in increasing severity:
| Level | Value | When to use |
|---|---|---|
DEBUG | 10 | Detailed diagnostic info โ only useful for debugging |
INFO | 20 | Confirmation that things are working as expected |
WARNING | 30 | Something unexpected happened, but the program continues |
ERROR | 40 | A serious problem โ the program couldn't do something |
CRITICAL | 50 | A fatal error โ the program may not be able to continue |
logging.basicConfig()
The simplest setup โ configures the root logger. Call this once, early in your program:
Important: basicConfig() only works if the root logger has no handlers yet. It has no effect if called after logging has already been configured.
Format String Variables
| Variable | Meaning |
|---|---|
%(asctime)s | Human-readable time |
%(name)s | Logger name |
%(levelname)s | Level name (DEBUG, INFO, โฆ) |
%(message)s | The log message |
%(filename)s | Source filename |
%(lineno)d | Line number in source |
%(funcName)s | Function name |
logging.getLogger(name)
In any module, get a logger named after the module:
Using __name__ creates a hierarchy that mirrors your package structure. You can configure logging for myapp and it automatically applies to myapp.database, myapp.api, etc.
Handlers โ Where Logs Go
A handler sends log records to a destination. One logger can have multiple handlers:
Formatters โ How Logs Look
Attach a Formatter to a handler to control the output format:
Logging Exceptions
Log exceptions with their full traceback:
Logger Propagation
By default, log records propagate up to parent loggers. The root logger is the ultimate parent:
Logging Context with extra={}
Pass extra fields to add contextual information to log records:
Best Practices
- Never use
print()in library code โ it pollutes user output. Uselogging. - Use
logging.getLogger(__name__)โ not the root logger directly. - Set levels on handlers, not messages โ one codebase, multiple deployment configurations.
- Log the why, not the what โ "Connection failed after 3 retries" not "Error occurred".
- Use lazy formatting:
logger.debug("Value: %s", expensive_repr)โ the string isn't formatted if DEBUG is disabled.
Knowledge Check
What is the correct order of Python logging levels from lowest to highest severity?
Why should you use `logging.getLogger(__name__)` instead of using the root logger directly?
What is the most efficient way to log a debug message that involves an expensive string operation?