Card 05/ 08
Single Responsibility in the Wild: Python's logging Module
Loggers decide what to record, handlers decide where it goes, formatters decide how it looks.
Python's standard logging module is a library split along reasons to change, and you have probably used it without seeing the split. This card shows its three main classes and the job each one owns. It follows the same rule as splitting a payroll class by department: one class per party that asks for changes. Here the parties are not teams but concerns: what gets recorded, where it is sent, and how it looks.
The listing creates a logger at level INFO, attaches a handler that writes to standard output, and swaps the formatter halfway through.
import logging
import sys
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(levelname)s %(name)s: %(message)s"))
logger = logging.getLogger("shop.checkout")
logger.setLevel(logging.INFO)
logger.addHandler(handler)
logger.debug("basket has 3 lines")
logger.info("order 1042 placed")
handler.setFormatter(logging.Formatter("[%(name)s] %(message)s"))
logger.warning("payment retried")INFO shop.checkout: order 1042 placed
[shop.checkout] payment retriedThree things happened, each in a different class. The debug message was dropped because the logger was set to INFO, so deciding what is worth recording is the logger's job. Every message went to standard output because of the handler; replacing it with a FileHandler would send the same records to a file without touching the logger. The text changed shape on the last line because only the formatter was replaced.
Each of those is a separate reason to change. Operations changes where logs go, a developer changes how much detail a module records, and whoever reads the logs changes their format. Because each concern has its own class, any one can change without the others noticing.
← → move through sections and cards