Q&A library Python

How do I use logging instead of print in Python?

Python Advanced debuggingloggingoutput

Short answer

Use the logging module to record messages with levels like info and error, so you can control what appears and where without deleting print statements. Configure it once, and get a named logger per module.

The short version

Use the logging module instead of print for anything beyond a quick script. It records messages with a severity level (debug, info, warning, error, critical), so you can turn detail up or down and send output to a file, all without editing individual statements. Configure logging once at the start, and get a logger per module.

The basic setup

import logging

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

logger.info("processing started")
logger.warning("skipping an empty row")
logger.error("could not read the file")

basicConfig sets things up once, getLogger(__name__) gives a logger named after the module, and each call records a message at its level.

Why levels beat print

With print, every message always appears, so you end up adding and deleting prints as you debug. With logging, each message has a level, and you set a threshold. Set the level to INFO for normal running and DEBUG when investigating a problem, and the detailed messages appear or disappear without touching the code.

logging.basicConfig(level=logging.DEBUG)   # now debug messages show too

This means you can leave informative logging in place permanently, rather than stripping out debug prints before shipping.

Sending output to a file

A big advantage over print is directing output somewhere useful, such as a log file, which basicConfig supports directly.

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

The format adds a timestamp, the level and the logger name to each line, so logs are searchable and tell you when and where each event happened, which bare prints do not.

Logging exceptions

Inside an except block, logger.exception records the message and the full traceback automatically, which is invaluable for diagnosing failures.

try:
    risky()
except Exception:
    logger.exception("risky() failed")

This captures the stack trace without you formatting it, so a failure in production leaves a full record.

The per-module logger pattern

Getting a logger with getLogger(__name__) in each module means log lines are tagged with where they came from, and you can raise or lower the level for one noisy module without affecting the rest. This is why libraries use named loggers rather than printing.

Configure once, at the top level

Call basicConfig (or a fuller configuration) once, in your program's entry point, not in library code. Libraries should only get a logger and emit messages, leaving the application to decide the level and destination. This separation lets the same library be quiet in one program and verbose in another.

Do not use print for real applications

For a throwaway script, print is fine. For anything that runs unattended, gets deployed, or that you will need to debug later, logging is the right tool: it records when things happened, at what severity, from where, and lets you control all of that from one place.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project