Understand the contract
Logging separates the event a program records from where and how that event is displayed. A logger names the source, a level expresses severity, a handler sends accepted records to a destination, and a formatter defines their presentation.
DEBUG is detailed diagnostic information; INFO records ordinary milestones; WARNING identifies a concern; ERROR records a failed operation; CRITICAL indicates a severe failure. Set thresholds deliberately so useful diagnostics can be enabled without rewriting application logic.
Follow the runtime behavior
Both logger and handler levels can filter messages. Propagation sends records to ancestor handlers and can produce duplicates if handlers exist at multiple levels. These examples clear handlers on their own named logger and disable propagation for reproducible output.
StreamHandler defaults to stderr. Each example explicitly passes sys.stdout so the shown output is captured consistently. Production applications should configure handlers at their entry point; reusable libraries generally obtain a named logger without imposing global configuration.
Apply it and practise
Pass values as separate logging arguments, such as logger.info("count=%s", count), instead of eagerly formatting a string. Avoid logging passwords, tokens, or unnecessary personal information.
Exercise: configure one stdout handler with level WARNING and a logger with level DEBUG. Emit DEBUG, INFO, and WARNING records and predict which survives. Then lower only the handler threshold and explain the changed output.
Syntax
logger.info("Processed %s items", count)Examples
Filter by severity
Use levels to retain operational milestones while hiding verbose diagnostics by default.
import logging
import sys
logger = logging.getLogger("lesson.levels")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.debug("details")
logger.info("ready")
logger.warning("low stock")- The INFO logger threshold removes DEBUG before delivery. The handler writes the remaining records to stdout and the formatter adds each level name.
- Use levels to retain operational milestones while hiding verbose diagnostics by default.
- Clearing handlers is appropriate here for an isolated teaching logger, not for reconfiguring another library's logger.
- Expected output: INFO: ready WARNING: low stock
Include the source name
Use module or subsystem names to distinguish messages from several application components.
import logging
import sys
logger = logging.getLogger("inventory")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(name)s | %(message)s"))
logger.addHandler(handler)
logger.info("Processed %s items", 4)- The formatter includes the logger name. Logging substitutes the separate argument into the message when formatting the record, preserving a stable message template.
- Use module or subsystem names to distinguish messages from several application components.
- Argument expressions still execute before the logging call; lazy string substitution does not defer an expensive function used as an argument.
- Expected output: inventory | Processed 4 items
Apply a handler threshold
Use separate handler thresholds when one destination should receive only important failures.
import logging
import sys
logger = logging.getLogger("lesson.destination")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.ERROR)
handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
logger.addHandler(handler)
logger.warning("retrying")
logger.error("operation failed")- The logger accepts both records, but the handler rejects WARNING because its threshold is ERROR. Filtering can therefore occur at more than one stage.
- Use separate handler thresholds when one destination should receive only important failures.
- This records a failure message without traceback details; logger.exception inside a handler can include a traceback when diagnosis requires it.
- Expected output: ERROR operation failed
Best practices
- StreamHandler defaults to stderr. Each example explicitly passes sys.stdout so the shown output is captured consistently. Production applications should configure handlers at their entry point; reusable libraries generally obtain a named logger without imposing global configuration.
- Pass values as separate logging arguments, such as logger.info("count=%s", count), instead of eagerly formatting a string. Avoid logging passwords, tokens, or unnecessary personal information.
Runtime & versions
Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
Python language reference