Python Logging Patterns
Logging patterns are habits for what log messages say and where loggers live. Each module makes its own logger with logging.getLogger(__name__).
One logger per module
Each part of a program makes its own logger. The name in each line shows where the message came from.
import logging, sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(name)s: %(message)s")
orders = logging.getLogger("shop.orders")
stock = logging.getLogger("shop.stock")
orders.info("Order 42 placed")
stock.info("Stock checked")Log the whole error
This fragment needs a logger and a load_count function. Call logger.exception inside the except block, and it adds the traceback for you.
try:
count = load_count("abc")
except ValueError:
logger.exception("Could not load count")Pass values the lazy way
Put placeholders in the message text. Pass the real values as extra arguments after the text.
import logging, sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(message)s")
logger = logging.getLogger("demo")
name = "Ana"
logger.info("User %s logged in", name)
logger.info("Score: %d", 95)Leave secrets out
Log who acted, not their secret. A user name is fine in a log. A password never is.
username = "ana"
logger.info("Login attempt for user %s", username)
# Never log the password itself.
# Never log a whole database row or a whole file.Example
import logging, io
stream = io.StringIO()
handler = logging.StreamHandler(stream)
handler.setFormatter(logging.Formatter("%(levelname)s %(name)s: %(message)s"))
load_logger = logging.getLogger("loader")
app_logger = logging.getLogger("app")
for one_logger in (load_logger, app_logger):
one_logger.setLevel(logging.DEBUG)
one_logger.addHandler(handler)
one_logger.propagate = False
def load_count(text):
return int(text)
def main():
app_logger.info("Load started")
try:
count = load_count("not-a-number")
except ValueError:
load_logger.exception("Could not load count")
count = 0
lines = stream.getvalue().splitlines()
print(lines[0])
print(lines[1])
print(lines[-1])
print("Count is", count)
main()Key points
- Use one logger per module, made with logging.getLogger(__name__)
- Use logger.exception(...) inside an except block to log an error with its traceback
- Lazy arguments: values passed after the message text, such as the name in logger.info("User %s", name)
- Logging fills in the % placeholders only when the message will show
- Never log secrets such as passwords, access tokens, or full card numbers
- An f-string builds its text at once, even when the level hides the message
- Logging patterns cover what messages say and where loggers live.
In this example, load_count is a small fragile loader. It turns text into a number with int(...), and bad text raises ValueError. The main code catches that error and calls load_logger.exception(...), so the log keeps the full traceback. Two loggers share one handler here. In a real app, each file would make its own logger with __name__. The log goes to a text stream in memory. The code prints only the first line, the second line, and the last line. The real log also holds traceback lines between the ERROR line and the ValueError line. Those middle lines carry file paths and line numbers, so they can change from run to run. The count falls back to 0, and the program ends in a clean state.
Note
The % style waits until logging knows the message will show.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
Which call lets logging skip building the message text when INFO is turned off?