Skip to content

How to Replace print() with Logging in Python

Learn how to migrate from print() statements to Python's logging module for better control over output destinations, severity levels, and production debugging.

When transitioning from quick scripts to production-ready applications, understanding when and how to replace print() statements with proper logging becomes essential.

This guide walks you through the process and explains why this change matters for maintainable code.

Why Move Beyond print()

While print() works perfectly for quick debugging during development, it lacks the flexibility needed for production environments where you need control over output destinations, severity levels, and message formatting.

Feature print() logging
Output destination Console only Files, console, email, HTTP endpoints
Severity levels None DEBUG, INFO, WARNING, ERROR, CRITICAL
Metadata Manual formatting Automatic timestamps, line numbers, module names
Runtime control Requires code changes Single configuration change
Performance Always executes Can disable by level

Setting Up Basic Logging

The simplest way to start using logging requires just two lines of code:

import logging

logging.basicConfig(level=logging.INFO)

logging.info("Application started successfully")
logging.warning("Configuration file not found, using defaults")
logging.error("Failed to connect to database")

Output:

INFO:root:Application started successfully
WARNING:root:Configuration file not found, using defaults
ERROR:root:Failed to connect to database

Configuring File Output

Production applications typically write logs to files for later analysis:

import logging

logging.basicConfig(
    filename='application.log',
    format='%(asctime)s - %(levelname)s - %(message)s',
    level=logging.DEBUG,
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.debug("Processing user request")
logging.info("User authentication successful")
logging.warning("API rate limit approaching")
logging.error("Payment processing failed")
logging.critical("Database connection lost")

File content:

2025-01-15 14:32:01 - DEBUG - Processing user request
2025-01-15 14:32:01 - INFO - User authentication successful
2025-01-15 14:32:02 - WARNING - API rate limit approaching
2025-01-15 14:32:02 - ERROR - Payment processing failed
2025-01-15 14:32:03 - CRITICAL - Database connection lost

Understanding Logging Levels

Each level serves a specific purpose in categorizing your messages:

Level Numeric Value When to Use
DEBUG 10 Detailed diagnostic information for developers
INFO 20 Confirmation that things work as expected
WARNING 30 Something unexpected happened but the app still works
ERROR 40 A function failed to execute properly
CRITICAL 50 The application cannot continue running
import logging

logging.basicConfig(level=logging.WARNING)

logging.debug("This won't appear")    # Below threshold
logging.info("This won't appear")     # Below threshold
logging.warning("This will appear")   # Meets threshold
logging.error("This will appear")     # Above threshold

Output:

WARNING:root:This will appear
ERROR:root:This will appear

Creating Custom Loggers

For larger applications, create dedicated loggers for different modules:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('debug.log')

console_handler.setLevel(logging.WARNING)
file_handler.setLevel(logging.DEBUG)

console_format = logging.Formatter('%(levelname)s - %(message)s')
file_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format)

logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.debug("Detailed info for file only")
logger.warning("Warning shown in console and file")

Logging Exceptions

Capture full stack traces when exceptions occur:

import logging

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

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        logging.exception("Division by zero attempted")
        return None

result = divide(10, 0)

Output:

2025-01-15 14:35:22 - ERROR - Division by zero attempted
Traceback (most recent call last):
  File "example.py", line 10, in divide
    return a / b
ZeroDivisionError: division by zero

Migrating from print() to logging

Here's a practical before-and-after comparison:

Before (using print)

def process_order(order_id):
    print(f"Processing order {order_id}")
    
    if not validate_order(order_id):
        print(f"ERROR: Order {order_id} is invalid")
        return False
    
    print(f"Order {order_id} validated successfully")
    return True

After (using logging)

import logging

logger = logging.getLogger(__name__)

def process_order(order_id):
    logger.info(f"Processing order {order_id}")
    
    if not validate_order(order_id):
        logger.error(f"Order {order_id} is invalid")
        return False
    
    logger.debug(f"Order {order_id} validated successfully")
    return True

Quick Reference

Situation Use This
Temporary debugging during development print()
Any code going to production logging
Tracking application flow logging.info()
Recording errors with context logging.error()
Capturing exceptions with tracebacks logging.exception()

Making the switch from print() to logging is an important step in writing professional, maintainable Python code. The initial setup takes only a few minutes, but the benefits in debugging, monitoring, and maintaining production applications are substantial.

Navigation