Message arguments and formats

logging · Essentials · 9 min

Lazy arguments save work and keep messages groupable; format attributes add context for free.

Logging methods take a message template and arguments: log.info("user %s logged in from %s", user, ip). The % formatting happens only if the message is actually emitted, so a dropped DEBUG call costs almost nothing. With an f-string, the string is built every time, even when the message is filtered out.

Arguments also keep the template constant ("user %s logged in"), which log aggregators such as Sentry use to group identical events. Use %s, %d, %r, %.2f; the message template itself always uses %-style, regardless of the formatter style.

The line layout is set by a format string of LogRecord attributes:

  • %(asctime)s, %(levelname)s, %(name)s, %(message)s,
  • %(module)s, %(funcName)s, %(lineno)d, %(process)d, %(threadName)s.

You can use {-style or $-style for the format string by passing style="{" to basicConfig or Formatter: "{asctime} {levelname:<8} {message}". datefmt controls asctime using time.strftime codes.

If a message has placeholders but the arguments don't match, logging doesn't crash your program; it prints a "--- Logging error ---" traceback to stderr and moves on.

Example

import logging, sys
logging.basicConfig(
    stream=sys.stdout, level=logging.DEBUG, style="{",
    format="{levelname:<7} {funcName}:{lineno} {message}",
)
log = logging.getLogger(__name__)

class Expensive:
    def __str__(self):
        print("  (str called)")
        return "big object"

def handle(user, ms):
    log.info("user %s served in %.1f ms", user, ms)
    log.setLevel(logging.INFO)
    log.debug("state: %s", Expensive())   # never formatted: no '(str called)'

handle("ada", 12.345)
# INFO    handle:14 user ada served in 12.3 ms

Remember

  • Pass values as arguments: log.info('x=%s', x), not f-strings
  • Formatting is lazy: skipped when the message is filtered out
  • Format strings use LogRecord attributes like %(name)s and %(lineno)d

Watch out for

  • Using f-strings in hot debug logging, paying formatting costs even when DEBUG is off.

Check yourself

  1. Why prefer log.debug('data=%s', data) over log.debug(f'data={data}')?

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#logrecord-attributes