Logging
Logging is the standard-library way to record what a program did, at levels from debug to critical, written to the console or a file.
Last updated: 13 Oct, 2026 · Python 3.12
The tests in pytest.raises and parametrize checked the code before it ships. Logging records what the code does while it runs. Up to now every program used print; print cannot say how important a line is, be turned down to warnings only, or be sent to a file without changing the code. The logging module, part of Python, does all three.
Syntax:
import logging
logging.basicConfig(level=logging.INFO) # show INFO and above
logging.info("a normal event") # debug, info, warning, error, criticalThe five levels
The levels go from least to most serious. Each one is a function on logging:
logging.debug("smallest detail, for finding a bug")
logging.info("a normal event")
logging.warning("something to look at")
logging.error("something failed")
logging.critical("the program cannot go on")Setting the level with basicConfig
import logging
import sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout,
format="%(levelname)s %(message)s")
logging.debug("connecting to the model")
logging.info("loaded 5 tickets")
logging.warning("ticket 4 needs a person")INFO loaded 5 tickets WARNING ticket 4 needs a person
basicConfig sets logging up once. level=logging.INFO is a floor: INFO and above appear, so the debug line is dropped. format controls each line; here it shows the level and the message. stream=sys.stdout sends it to the screen, which is also where logging goes by default.
Naming a logger
import logging
import sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout,
format="%(name)s %(levelname)s %(message)s")
logger = logging.getLogger("triage")
logger.info("ticket 1 sorted as billing")
logger.warning("ticket 4 needs a person")triage INFO ticket 1 sorted as billing triage WARNING ticket 4 needs a person
logging.getLogger("triage") makes a named logger, and %(name)s prints that name on every line. In a program with several parts you can see which part wrote each line, and turn one part's logging up or down on its own. Every library names its own logger this way.
Logging to a file
import logging
logging.basicConfig(filename="run.log", filemode="w", level=logging.INFO,
format="%(name)s %(levelname)s %(message)s")
logging.getLogger("triage").info("sorted 5 tickets")
logging.getLogger("triage").warning("ticket 4 needs a person")
print(open("run.log").read())triage INFO sorted 5 tickets triage WARNING ticket 4 needs a person
filename sends the log to a file instead of the screen, and filemode="w" starts it fresh each run, where "a" would add to the end. Reading the file back shows the two lines. A running service keeps its log in a file so you can read what happened after the fact.
Adding a timestamp
Two fields turn each line into a dated record:
logging.basicConfig(
filename="run.log", filemode="w", level=logging.INFO,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)%(asctime)s adds the time to each line and datefmt sets how it looks, so a line reads 2026-10-13 09:30:01 triage INFO sorted 5 tickets.
print vs logging
print | logging | |
|---|---|---|
| Levels | One, always shown | debug to critical, filtered by level |
| Where it goes | The screen | Screen or file, set in one place |
| In a library | Noise you cannot turn off | A named logger you can turn down |
Where logging shows up in AI code
- Recording each model call, its tokens and whether it was retried, to a file you can read later.
- Turning a noisy library down to warnings while keeping your own logger at info.
- Separating normal events (info) from failures (error) so an alert can watch for errors only.
basicConfig only does something the first time it runs; a second call with new settings is ignored. Set it once, early, before any logging happens.Related
- Previous: pytest.raises and parametrize
- Next: Project: ticket triage
- Reference: logging in the Python docs
- Change
level=logging.INFOtologging.DEBUGin the named-logger example and run it again. - Call
logging.getLogger("noisy").setLevel(logging.WARNING), then aninfoon it, and see that nothing prints. - Change
filemode="w"to"a", run the file example twice, and readrun.log.
Little by little, you're building something great.