Basic Setup
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger.debug("Diagnostic info")
logger.info("Normal event")
logger.warning("Unexpected but non-fatal")
logger.error("Something failed")
logger.critical("System cannot continue")File & Rotating Handlers
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
fmt = logging.Formatter("%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s")
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(fmt)
# Rotate at 5 MB, keep 3 backups
file_h = RotatingFileHandler("app.log", maxBytes=5_000_000, backupCount=3)
file_h.setLevel(logging.DEBUG)
file_h.setFormatter(fmt)
logger.addHandler(console)
logger.addHandler(file_h)Structured JSON Logging
import json
class JSONFormatter(logging.Formatter):
def format(self, record):
entry = {
"time": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
entry["exception"] = self.formatException(record.exc_info)
return json.dumps(entry)
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logging.getLogger("api").addHandler(handler)Logging Exceptions
try:
result = 10 / 0
except ZeroDivisionError:
logger.exception("Division failed") # Auto-includes traceback
logger.info("User login", extra={"user_id": 42, "ip": "127.0.0.1"})Logging Beats print() β Here's Why
print() is fine for a quick check, but real applications use the logging module. The difference: logging has severity levels you can filter, can route output to files/services, and can be turned down in production without editing code.
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
log.debug("detailed trace") # hidden unless level=DEBUG
log.info("server started")
log.warning("disk 80%% full")
log.error("payment failed", exc_info=True) # includes the traceback
| Level | Use for |
|---|---|
| DEBUG | developer diagnostics |
| INFO | normal events |
| WARNING | something unexpected but handled |
| ERROR / CRITICAL | failures |
Key practices: set the level once at startup β flip it from INFO to DEBUG to get more detail with zero code changes, the whole point over print. Use getLogger(__name__) per module so you can see where messages came from. Pass exc_info=True (or use log.exception() inside an except) to capture the stack trace. Security: never log passwords, tokens, or full card numbers β logs get shipped, stored, and read by many people.
ποΈ Practical Exercise
Set up proper logging:
- Configure logging with
logging.basicConfigand log messages at different levels. - Add a file handler so logs are written to a file as well as the console.
- Use a
RotatingFileHandlerto cap log file size. - Log an exception with
logging.exception()inside anexceptblock.
π₯ Challenge Exercise
Add structured logging to a small script: create a named logger, log at DEBUG/INFO/WARNING/ERROR levels appropriately, write to both console and a rotating file, and format messages with timestamp, level, and module. In an error path, capture the full traceback with logging.exception. Bonus: emit logs as JSON lines so they can be ingested by a log aggregator.
π Summary
- The
loggingmodule provides leveled, configurable, and reusable diagnostics β far better thanprint. - Levels in order: DEBUG, INFO, WARNING, ERROR, CRITICAL.
- A logger emits records, handlers route them (console, file), and formatters shape their text.
logging.exception()logs an error message together with the current traceback.- Rotating handlers cap log size by rolling over to new files.
- Structured/JSON logs are easy for tools to parse, search, and aggregate.
Interview Questions on Logging
- Why use the
loggingmodule instead ofprint? - What are the standard logging levels and when do you use each?
- What is the difference between a logger, a handler, and a formatter?
- What does
logging.exception()add overlogging.error()? - What is a rotating file handler and why is it useful?
- How do you configure logging for a larger application?
- What is structured (e.g. JSON) logging and why does it help?
Related Topics
FAQ
logging better than print? +Logging adds severity levels, timestamps, and source info, can be routed to files or external systems, and can be turned up or down without editing code. Prints have to be removed manually and offer none of this control.
A logger is the entry point you call (logger.info(...)). A handler decides where records go (console, file, network). A formatter defines how each record looks (timestamp, level, message). One logger can have several handlers.
DEBUG for detailed developer diagnostics, INFO for normal events, WARNING for unexpected-but-handled situations, ERROR for failures in an operation, and CRITICAL for severe problems threatening the whole program.
logging.exception() do? +Called inside an except block, it logs your message at ERROR level and automatically appends the full traceback, so you capture both the context and the stack of the failure in one call.
