Skip to content
  • Logging · Python

Logging

LearnViaNotes @learnvianotes· 3 min read

logging records what a program is doing — with severity levels, timestamps and destinations you can change without touching the code. It is how production code replaces print.

Why not print?

printlogging
SeverityNoneDEBUG → CRITICAL, filterable
Destinationstdout onlyConsole, files, syslog, HTTP… (handlers)
MetadataOnly what you typeTime, module, line, thread, level (formatters)
Turn off / onEdit the codeChange the level or config
LibrariesPollute the user's outputSilent unless the app configures logging

Levels

LevelValueUse for
DEBUG10Detailed diagnostics while developing
INFO20Normal events: started, user logged in, job finished
WARNING30Something unexpected but handled — the default threshold
ERROR40An operation failed
CRITICAL50The program may not be able to continue

Quick start

Python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
log = logging.getLogger(__name__)      # one logger per module

log.debug("not shown: below INFO")
log.info("server started on port %d", 8000)
log.warning("disk %s%% full", 91)
Output
2026-09-29 14:05:09,123 INFO __main__: server started on port 8000
2026-09-29 14:05:09,124 WARNING __main__: disk 91% full

Loggers, handlers, formatters

Logger app.db → level filter → Handler(s) → Formatter → Output

Python
import io, logging

stream = io.StringIO()                                   # stands in for a console or file
handler = logging.StreamHandler(stream)
handler.setFormatter(logging.Formatter("%(levelname)-7s %(name)s | %(message)s"))

log = logging.getLogger("shop.orders")
log.setLevel(logging.DEBUG)
log.addHandler(handler)
log.propagate = False                                    # don't also send to the root logger

log.debug("cart has %d items", 3)
log.info("order %s placed", "A-17")
log.getChild("payments").error("card declined")          # child logger: shop.orders.payments

for line in stream.getvalue().splitlines():
    print(line)
Output
DEBUG   shop.orders | cart has 3 items
INFO    shop.orders | order A-17 placed
ERROR   shop.orders.payments | card declined

Logging exceptions

Python
import io, logging

stream = io.StringIO()
log = logging.getLogger("demo.exc")
log.addHandler(logging.StreamHandler(stream))
log.propagate = False

try:
    1 / 0
except ZeroDivisionError:
    log.exception("could not compute ratio")      # ERROR level + full traceback

lines = stream.getvalue().splitlines()
print(lines[0])            # could not compute ratio
print(lines[1])            # Traceback (most recent call last):
print(lines[-1])           # ZeroDivisionError: division by zero

Configuring a real application

Python
import logging.config

logging.config.dictConfig({
    "version": 1,
    "formatters": {"plain": {"format": "%(asctime)s %(levelname)s %(name)s: %(message)s"}},
    "handlers": {
        "console": {"class": "logging.StreamHandler", "formatter": "plain", "level": "INFO"},
        "file": {"class": "logging.handlers.RotatingFileHandler", "formatter": "plain",
                 "filename": "app.log", "maxBytes": 1_000_000, "backupCount": 3},
    },
    "loggers": {"urllib3": {"level": "WARNING"}},          # quieten a chatty library
    "root": {"level": "DEBUG", "handlers": ["console", "file"]},
})

Best practices

  • One logger per module: log = logging.getLogger(__name__).

  • Libraries add logging.NullHandler() to their top-level logger and never call basicConfig.

  • Use log.exception(...) inside except blocks to keep the traceback.

  • In services, log structured data (JSON lines) so log platforms can search fields — python-json-logger or structlog.

Why is logging.getLogger(__name__) the recommended pattern?

What is the default logging level?

Why use %s arguments instead of f-strings in log calls?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report