Python logging — Structured Application Logs
The logging module is the standard way to record what your program is doing. Unlike print, it has severity levels, can route messages to files or the network, includes timestamps and source info, and can be turned up or down without touching your code.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("App started")
logging.warning("Low disk space")
logging.error("Connection failed")
# INFO:root:App started
# WARNING:root:Low disk space
# ERROR:root:Connection failedWhy not just use print?
Section titled “Why not just use print?”print | logging |
|---|---|
| One firehose to stdout. | Severity levels you can filter. |
| No timestamps or context. | Timestamps, module, line number, etc. |
| Edit code to silence it. | Change one config line. |
| Goes one place. | Files, console, syslog, email — many handlers. |
Log levels
Section titled “Log levels”Each message has a level. The logger only emits messages at or above its configured level.
| Level | Value | Use for |
|---|---|---|
DEBUG | 10 | Detailed diagnostic info. |
INFO | 20 | Normal events (“server started”). |
WARNING | 30 | Something unexpected, but still working (the default). |
ERROR | 40 | A failure in some operation. |
CRITICAL | 50 | A serious failure; the program may stop. |
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("variable x = 42") # shown only because level is DEBUG
logging.info("processing record")
logging.warning("retrying")
logging.error("gave up")
print(logging.getLevelName(20)) # INFOThe default level is
WARNING, sodebugandinfomessages are hidden until you lower the level withbasicConfig(level=...).
basicConfig — quick setup
Section titled “basicConfig — quick setup”basicConfig configures the root logger in one call. Set it once, early, before logging anything.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
logging.info("ready")
# 14:30:01 [INFO] root: ready
# Write to a file instead of the console
logging.basicConfig(filename="app.log", level=logging.INFO)Common format placeholders
Section titled “Common format placeholders”| Placeholder | Inserts |
|---|---|
%(asctime)s | Human-readable timestamp. |
%(levelname)s | The level name (INFO, ERROR, …). |
%(name)s | The logger’s name. |
%(message)s | The log message. |
%(filename)s / %(lineno)d | Source file / line number. |
Named loggers — one per module
Section titled “Named loggers — one per module”Instead of the root logger, create a logger per module with getLogger(__name__). This tags every message with where it came from and lets you tune modules independently.
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("module-specific message")
# INFO:__main__:module-specific messageHandlers and formatters
Section titled “Handlers and formatters”For real apps, attach handlers (where logs go) and formatters (how they look). You can have several at once — e.g. INFO to console, ERROR to a file.
import logging
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Console handler at INFO
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
# File handler at DEBUG
file = logging.FileHandler("debug.log")
file.setLevel(logging.DEBUG)
logger.addHandler(console)
logger.addHandler(file)
logger.debug("only in the file")
logger.info("console + file")Logging exceptions
Section titled “Logging exceptions”Inside an except block, logger.exception(...) logs the message plus the full traceback.
import logging
logging.basicConfig(level=logging.ERROR)
try:
1 / 0
except ZeroDivisionError:
logging.exception("Math went wrong")
# ERROR:root:Math went wrong
# Traceback (most recent call last): ...Common pitfalls
Section titled “Common pitfalls”- Default level hides INFO/DEBUG — call
basicConfig(level=logging.DEBUG)to see them. basicConfigonly works once — the first call wins; later calls are ignored unlessforce=True.- Use
%slazy formatting:logger.info("user %s", name), not f-strings, so the string is only built if the message is actually emitted. - Don’t log secrets — passwords, tokens, and personal data should never hit the logs.
Practice Exercises
Section titled “Practice Exercises”Exercise 1 – Level names and values
Section titled “Exercise 1 – Level names and values”Exercise 2 – Configure and log info
Section titled “Exercise 2 – Configure and log info”Exercise 3 – Create a named logger
Section titled “Exercise 3 – Create a named logger”Summary
Section titled “Summary”loggingreplacesprintwith leveled, timestamped, routable messages.- Levels:
DEBUG < INFO < WARNING < ERROR < CRITICAL; default isWARNING. - Configure quickly with
basicConfig(level=..., format=...). - Use
getLogger(__name__)per module, and attach handlers/formatters for fine control. - Log tracebacks with
logger.exceptioninsideexceptblocks.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading