logging in Python: keeping a record of what a program does

logging keeps a timestamped record of what a program does while it runs, with severity levels you can filter without touching a line of code.
5 min read
Believemy logo

Definition

A program running with nobody watching the screen ends up sooner or later running into something unexpected. The knowing comes after the fact: without any written trace along the way, all that is left is the final result, which never tells the story that led up to it.

logging is the standard library module that writes this story as the program moves forward. Every message leaves with a timestamp, a severity level and the name of the place it came from, then lands wherever the configuration sends it: terminal, file, collection service. The code says what happened, the configuration decides who hears about it.

PYTHON
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info("Import started, %s rows to process", 1200)
logger.warning("Row 47 skipped, unreadable date")

The __name__ handed to getLogger is not a style detail: it sticks the exact origin onto the message, and later lets one chatty file be silenced without silencing the others.


The first instinct while writing code is to drop in a print to watch a value go by, which works as long as somebody is watching the terminal. Once the program ships, those print calls become permanent noise, or messages someone would have wanted to keep.

logging repeats the same gesture, but adds what a plain print cannot do alone: an automatic timestamp, a level that can be filtered after the fact, a destination that can be changed without touching the code. Here are the two side by side:

NeedWith printWith logging
Seeing a value while writing the codePerfectNeedlessly heavy
Knowing what time it happenedWrite it yourselfAutomatic timestamp
Cutting the noise in productionDelete the linesRaise a threshold
Writing to a fileRedirect the terminalAdd a handler
Keeping the stack of a crashBy handA single method

There are cases where logging brings nothing, a thirty-line script for instance. More importantly: a program that prints its result must use print, never logging, the first answering the person waiting, the second narrating behind the scenes what the program is doing.


The five levels

Writing a message at every step sounds like a good idea, until the terminal overflows and the one that matters drowns in the detail. logging answers by attaching a level to each message, compared against a configured threshold: only the messages that reach it get shown.

Here are the five levels, from the most common to the most severe:

LevelWhat it reports
DEBUGThe detail worth having while hunting a bug, invisible the rest of the time
INFOA normal step: the job starts, the file is written
WARNINGSomething odd that the program managed to work around
ERRORAn operation failed, the program carries on
CRITICALThe program cannot carry on

The default threshold is WARNING. A program that has just been configured therefore shows neither its DEBUG nor its INFO messages, which catches nearly everyone off guard the first time.


The first-day trap

"I call logger.info and nothing shows up." That is this same threshold surprise: the message is not lost, it is filtered. The fix takes one call, made once, at the entry point of the program, before any other message:

PYTHON
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s : %(message)s",
)

That call hides a second surprise, sneakier still: basicConfig does nothing once logging is already configured, and a first message emitted before the call configures it by default, without warning. A call made too late is therefore ignored in silence.

Warning

Anyone writing a library meant for others follows the opposite rule: never call basicConfig inside it, or you force your format onto the program installing it.


Recording an error without losing the traceback

Inside an except block, the reflex is to copy the exception message and move on. That throws away the most useful part: the traceback, which says on which line the problem was born. Without it, that path has to be pieced back together from memory, or the bug reproduced just to see it again.

The exception method attaches it on its own, and belongs only in a rescue block:

PYTHON
try:
    unit = invoice["amount"] / invoice["quantity"]
except ZeroDivisionError:
    logger.exception("Zero quantity on invoice %s", invoice["id"])
    unit = 0

Note the comma, where an f-string would have been tempting. The message stays a constant template, which lets a collection tool group a thousand occurrences of one incident instead of showing a thousand distinct lines. The performance gain sometimes cited stays negligible outside very hot loops.


Frequently asked questions

Question

One logger per file, or a single one for the whole program?

One per file, always with the same line at the top: logger = logging.getLogger(__name__). Loggers form a hierarchy built on the dots in the name, so shop.payment inherits the settings of shop, which allows a single module to be switched to DEBUG without drowning the rest.

Question

Should logs be written to a file?

It mostly depends on where the program runs. On a classic server, yes, with a handler that rolls the file over past a certain size, or it becomes unreadable. In a container, no: standard output is enough, the host collects and archives it, and that is usually how a FastAPI or Django application is deployed.

Question

What should never be written to a log?

A password, a token, a card number, the full contents of a form. A log is kept for months and ends up read by people with no access to the database. Record the identifier of the user involved rather than their data, and avoid writing a whole json response without knowing exactly what it holds.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.