Choosing and filtering levels

logging · Essentials · 8 min

Consistent levels are what make logs searchable and alerting possible.

Each level has a number: DEBUG 10, INFO 20, WARNING 30, ERROR 40, CRITICAL 50. A logger passes a message on only if its level is at or above the logger's effective level.

A practical guide:

  • DEBUG: details useful when diagnosing, such as payloads and branch decisions. Off in production.
  • INFO: normal milestones, such as startup, a job finishing, a user signing in.
  • WARNING: something unexpected that was handled, such as a retry or a deprecated option.
  • ERROR: an operation failed; someone should look.
  • CRITICAL: the program may not be able to continue.

logger.setLevel(level) sets a threshold; a logger left at NOTSET (0) inherits the level of its nearest ancestor that has one, ultimately the root (WARNING by default). logger.getEffectiveLevel() tells you the resolved value.

Levels accept names as strings: setLevel("DEBUG") works, which is handy when reading from an environment variable. logging.getLevelNamesMapping() (3.11+) gives the name-to-number dict.

When building a message is expensive (formatting a huge object), guard it with if log.isEnabledFor(logging.DEBUG): so the work is skipped when debug is off.

Example

import logging, os, sys
logging.basicConfig(stream=sys.stdout, format="%(levelname)s %(message)s")

log = logging.getLogger("worker")
print(log.level, log.getEffectiveLevel())    # 0 30  (NOTSET, inherits WARNING)

log.setLevel(os.environ.get("LOG_LEVEL", "INFO"))
print(logging.getLevelName(log.getEffectiveLevel()))  # INFO
log.debug("payload=%s", {"id": 1})           # dropped
log.info("job started")                      # INFO job started
if log.isEnabledFor(logging.DEBUG):
    log.debug("expensive dump: %s", "...")   # skipped entirely
print(logging.getLevelNamesMapping()["ERROR"])  # 40

Remember

  • DEBUG 10, INFO 20, WARNING 30, ERROR 40, CRITICAL 50
  • A NOTSET logger inherits its ancestor's level
  • isEnabledFor() skips expensive message preparation

Watch out for

  • Logging normal events at ERROR, which floods alerting and hides real failures.

Check yourself

  1. A logger 'app.db' is NOTSET; 'app' is set to DEBUG; root is WARNING. What is app.db's effective level?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/logging.html#logging-levels