Logging

Libraries for generating and working with logs.

Keep the built-in logging module until you need what a Python logging library adds: structlog's key-value logs in JSON, or Loguru's logger, ready on import.

How to choose:

  • A library other people import: logging, with only a NullHandler
  • Key-value events, pretty in development and JSON in production: structlog
  • An app already on logging that needs structured output: structlog, which wraps it
  • A script you want logging in with no setup: Loguru
  • Log files that rotate, expire, and compress: Loguru

Listed in editorial order. Click a column to re-sort the whole list.

Press / to search. Tap a tag to filter. Click any row for details.

Search and filter

Results

Row number Tags
(Python standard library) Logging facility for Python.
Structured logging made easy.
hynek/github.com/hynek/structlog / /99,487,005 downloads/month
Library which aims to bring enjoyable logging in Python.
Delgan/github.com/Delgan/loguru / /68,647,796 downloads/month
The observability platform for Python, from the makers of Pydantic.
pydantic/github.com/pydantic/logfire / /13,829,087 downloads/month

Logging guide

Every Python module can take part in the standard library's logging, so your app's log holds messages from third-party packages next to your own. In each module, create a logger with logging.getLogger(__name__), so logger names follow your package layout. Use basicConfig() for a quick setup, and move to dictConfig(), which the docs recommend for new applications. In a library, add no handler other than NullHandler and don't log to the root logger: handlers are for the app developer to pick.

structlog logs events in a context of key-value pairs instead of prose, so each entry is a dictionary instead of a string. Bind values to a logger, and every entry it logs carries them. In a web app, call clear_contextvars() at the start of each request, then bind_contextvars() for values like a request ID. Render pretty, colored output during development and JSON in production, since log aggregators parse JSON more easily. structlog can also wrap the standard library's logging and add structure to it, so an app already built on logging keeps it.

Loguru has one logger, ready to use after from loguru import logger, and it writes to stderr out of the box. Instead of handlers, formatters, and filters, you configure it with one function, add(). In an app, call remove() first to drop the default handler, then add() where your logs should go. Given a file path, add() handles rotation, retention, and compression, and serialize=True turns each message into JSON. In a library, call disable() instead of add(), and the app using it can enable() your logs again.

Whichever you pick, configure logging once, where your app starts, and have each module only get its logger. With the standard library, usually only the root logger needs configuring, since module loggers pass their messages up to it. structlog wants structlog.configure() on app initialization, and with Loguru, your other modules inherit the configuration from your entry point. Third-party packages that use the standard library's logging keep logging there. Send their messages to your output too: structlog formats them with ProcessorFormatter, and Loguru intercepts them with a handler.

Know a project that belongs here?

Tell us what it does and why it stands out.