---
title: Use Lazy Formatting in Logging Calls
impact: MEDIUM
impactDescription: Eager string interpolation in logging (f-strings or %) evaluates and allocates the string even when the log level is disabled, wasting CPU and memory in production.
tags: python, logging, performance, quality
---

## Use Lazy Formatting in Logging Calls

Python's `logging` module accepts a format string and arguments **separately**. The string interpolation only happens if the message will actually be emitted (i.e., the log level is enabled). Using f-strings or `%` interpolation beforehand evaluates the string unconditionally, even if `DEBUG` is disabled.

**Incorrect:**
```python
import logging

logger = logging.getLogger(__name__)

user_id = get_user_id()
payload = serialize(data)

# These always evaluate the f-string / % formatting, even if DEBUG is off
logger.debug(f"Processing user {user_id} with payload {payload}")
logger.info("Request from %s: %s" % (ip_address, request.path))
logger.warning("Slow query: " + str(query_time) + "ms")
```

**Correct:**
```python
import logging

logger = logging.getLogger(__name__)

# Formatting deferred — only if DEBUG is enabled
logger.debug("Processing user %s with payload %s", user_id, payload)
logger.info("Request from %s: %s", ip_address, request.path)
logger.warning("Slow query: %sms", query_time)

# For complex objects, use lazy repr via %r
logger.debug("Event received: %r", event)

# Exception info — use exc_info=True or logger.exception()
try:
    call_external_api()
except requests.RequestException as e:
    logger.error("External API failed for user %s: %s", user_id, e, exc_info=True)
    # or equivalently inside an except block:
    # logger.exception("External API failed for user %s", user_id)
```

**Tools:** Ruff `G004` (logging-f-string), `W1201` in Pylint (logging-not-lazy), `W1202` (logging-format-interpolation), `TRY400` (error-instead-of-exception), flake8-logging-format
