Logging — Beyond print()
IntermediateThe logging module gives leveled, timestamped, routable logs — DEBUG/INFO/WARNING/ERROR/CRITICAL — configured once and used via module-level loggers; print() is for humans, logging is for systems.
Overview
print() debugging dies the moment code ships: no timestamps, no severity, no way to silence or route output. logging fixes all three: levels filter noise (DEBUG in development, INFO+ in production), formatters add time/module/line automatically, handlers route to console, files or aggregators. The professional pattern is two lines: logger = logging.getLogger(__name__) in every module, one basicConfig (or dictConfig) at the entry point. exception() inside except blocks captures full tracebacks — the habit that makes 2 AM debugging survivable.
Levels, Configuration & Module Loggers
Configure once at startup; get a named logger per module. Level ordering: DEBUG < INFO < WARNING < ERROR < CRITICAL — the configured level is the minimum that gets through.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
logger = logging.getLogger(__name__) # per-module logger
logger.debug("cache warm-up details") # hidden (below INFO)
logger.info("server started on :8000")
logger.warning("disk 85% full")
logger.error("payment gateway timed out")
# 14:02:11 INFO __main__: server started on :8000
# 14:02:11 WARNING __main__: disk 85% full
# 14:02:11 ERROR __main__: payment gateway timed out
# Lazy formatting — string built ONLY if the level passes
user_id, ms = "asha", 42
logger.info("user %s served in %d ms", user_id, ms) # preferred
# vs logger.info(f"user {user_id}...") — f-string always evaluatesTracebacks & Log Files
logger.exception() inside an except block logs the message PLUS the full traceback. FileHandler (or RotatingFileHandler) persists logs; a logger can feed console and file simultaneously.
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("orders")
logger.setLevel(logging.INFO)
# Console + rotating file (1 MB x 3 backups)
console = logging.StreamHandler()
filelog = RotatingFileHandler("orders.log", maxBytes=1_000_000, backupCount=3)
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
console.setFormatter(fmt); filelog.setFormatter(fmt)
logger.addHandler(console); logger.addHandler(filelog)
def place_order(payload):
try:
amount = payload["amount"] / payload["qty"]
except (KeyError, ZeroDivisionError):
logger.exception("order failed: %s", payload) # + traceback!
raise
try:
place_order({"amount": 500, "qty": 0})
except Exception:
pass
# orders.log now contains the message AND the full stack traceKey Points to Remember
- 1getLogger(__name__) per module; configure levels/handlers once at the entry point
- 2DEBUG for dev detail, INFO for lifecycle, WARNING for smells, ERROR/CRITICAL for failures
- 3logger.exception() inside except logs the traceback automatically
- 4Use %-style lazy args in log calls; f-strings evaluate even when filtered out
Interview Questions
Sign in to ask AriaWhy is logging preferred over print in production code — give three concrete reasons.
What extra does logger.exception() capture vs logger.error()?
Ask Aria about Logging — Beyond print()
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.