LearnContact
Lesson 5420 min read

Logging

Learn why logging is better than print statements and how to use the logging module to record events with levels, timestamps, and file output.

Introduction

As programs grow, print() statements scattered through the code become difficult to manage. You cannot easily tell how important a message is, when it happened, or turn groups of messages on and off — and you certainly do not want debug output cluttering a real application's console.

Python's built-in logging module solves all of this. It provides a structured way to record what a program is doing, with severity levels, automatic timestamps, and the ability to send messages to the console, a file, or both.

What You Will Learn
  • Why logging is better than scattering print() statements.
  • The five standard log levels and when to use each.
  • How to configure logging with logging.basicConfig().
  • How to send log messages to a file instead of the console.
  • A practical example showing logging in a small program.

Why Not Just Use print()?

print() is fine for quick experiments, but it does not scale well for real applications. Logging fixes several problems that print() cannot solve on its own.

print() Statements

  • No severity levels — every message looks the same.
  • No automatic timestamps.
  • Cannot be turned off without deleting code.
  • Always goes to the console only.
  • Hard to filter in a large codebase.

logging Module

  • Messages have levels: DEBUG, INFO, WARNING, ERROR, CRITICAL.
  • Timestamps are added automatically.
  • Can be enabled/disabled globally by changing one setting.
  • Can be sent to the console, a file, or both.
  • Easy to filter by level or module.

The logging Module

The logging module is part of Python's standard library, so no installation is required. You import it and start logging messages immediately.

basic_logging.py
import logging

logging.warning("This is a warning message")
logging.error("This is an error message")
Output
WARNING:root:This is a warning message
ERROR:root:This is an error message

By default, logging only displays messages at WARNING level or higher, and prints them to the console with the level name and logger name ("root") attached.

Log Levels

Every log message has a severity level. Choosing the right level for each message makes it easy to filter logs later — for example, hiding routine DEBUG messages in production while still showing ERROR messages.

LevelNumeric ValueWhen to Use
DEBUG10Detailed information, useful only when diagnosing problems.
INFO20Confirmation that things are working as expected.
WARNING30Something unexpected happened, but the program still works.
ERROR40A serious problem — the program could not perform a function.
CRITICAL50A very serious error — the program may be unable to continue running.

DEBUG

Variable values, function entry/exit — useful while developing.

INFO

Server started, file saved, user logged in — normal operation.

WARNING

Deprecated feature used, low disk space — not broken yet.

ERROR

A file failed to save, a request failed — something broke.

CRITICAL

Database unreachable, out of memory — the app may crash.

basicConfig() Basics

logging.basicConfig() lets you configure the logging system once, near the start of your program — setting the minimum level to show, the message format, and where messages go.

basic_config.py
import logging

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")

logging.debug("Debugging details")
logging.info("Program started")
logging.warning("Low disk space")
logging.error("Failed to open file")
logging.critical("System out of memory")
Output
2026-07-25 10:15:32,101 - DEBUG - Debugging details
2026-07-25 10:15:32,101 - INFO - Program started
2026-07-25 10:15:32,102 - WARNING - Low disk space
2026-07-25 10:15:32,102 - ERROR - Failed to open file
2026-07-25 10:15:32,102 - CRITICAL - System out of memory
The level Parameter

Setting level=logging.DEBUG means every message at DEBUG or above is shown. Setting level=logging.WARNING (the default) would hide the DEBUG and INFO lines entirely.

Logging to a File

Instead of (or in addition to) printing to the console, logs can be written to a file using the filename parameter of basicConfig(). This is useful for reviewing what happened after a program has already finished running.

log_to_file.py
import logging

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

logging.info("Application started")
logging.warning("Cache is almost full")
logging.error("Could not connect to server")

print("Check app.log for the recorded messages")
Output
Check app.log for the recorded messages
app.log (contents)
2026-07-25 10:20:11,455 - INFO - Application started
2026-07-25 10:20:11,456 - WARNING - Cache is almost full
2026-07-25 10:20:11,456 - ERROR - Could not connect to server

Practical Example

Here is a small function that uses different log levels appropriately while performing a division, instead of relying on print() to report what happened.

divide.py
import logging

logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")

def safe_divide(a, b):
    logging.debug(f"Attempting to divide {a} by {b}")
    if b == 0:
        logging.error("Division by zero attempted")
        return None
    result = a / b
    logging.info(f"Division successful: {a} / {b} = {result}")
    return result

safe_divide(10, 2)
safe_divide(5, 0)
Output
DEBUG: Attempting to divide 10 by 2
INFO: Division successful: 10 / 2 = 5.0
DEBUG: Attempting to divide 5 by 0
ERROR: Division by zero attempted

Common Mistakes

Avoid These Mistakes
  • Calling basicConfig() more than once expecting it to reconfigure — it only takes effect the first time in most Python versions.
  • Forgetting that the default level is WARNING, so debug() and info() calls silently produce no output.
  • Leaving debug-level logging enabled in a production application, creating excessive log volume.
  • Using print() for error reporting instead of logging.error(), losing timestamps and severity context.
  • Not rotating or clearing log files, letting them grow indefinitely over time.

Best Practices

  • Call logging.basicConfig() once, early in your program.
  • Use the log level that matches the message's real importance.
  • Include a timestamp and level in your format string.
  • Log to a file for long-running programs so history is not lost.
  • Prefer f-strings or %-formatting in log messages over string concatenation.

Frequently Asked Questions

Is print() ever acceptable instead of logging?

For tiny, throwaway scripts it is fine. For anything larger, or code that will run unattended, logging is strongly preferred because it provides levels, timestamps, and configurable output.

What is the default log level if I do not call basicConfig()?

WARNING. That means debug() and info() messages are silently ignored until you explicitly configure a lower level.

Can I log to both the console and a file at the same time?

Yes, though it requires adding multiple handlers to a logger rather than just using the filename parameter of basicConfig(), which only writes to one destination.

What is the "root" logger I see in the output?

It is the default logger used when you call functions like logging.info() directly. Larger applications typically create named loggers with logging.getLogger(__name__) instead.

Does logging slow down my program significantly?

The overhead is minimal for typical applications, and the debugging benefits generally far outweigh the small performance cost.

Key Takeaways

  • Logging records events with severity levels and timestamps, unlike print().
  • The five standard levels are DEBUG, INFO, WARNING, ERROR, and CRITICAL.
  • logging.basicConfig() configures the level, format, and destination.
  • Logs can be sent to the console, a file, or both.
  • The default level is WARNING, hiding DEBUG and INFO messages unless configured otherwise.

Summary

Logging replaces scattered print() statements with a structured system for recording what a program is doing, at a level of detail you control, with output you can send anywhere.

In this lesson, you learned why logging beats print(), the five standard log levels, how to configure logging with basicConfig(), and how to log messages to a file for later review.

Lesson 54 Completed
  • You understand why logging is preferred over print() in real programs.
  • You know all five standard log levels and when to use them.
  • You can configure logging output with basicConfig().
  • You are ready to learn about command line arguments.
Next Lesson →

Command Line Arguments