Skip to content

Logging

Configure logging for backoff retry events.

Default Logger

Backoff uses the 'backoff' logger by default. It's configured with a NullHandler, so nothing is output unless you configure it.

Basic Setup

import logging

# Enable backoff logging
logging.getLogger("backoff").addHandler(logging.StreamHandler())
logging.getLogger("backoff").setLevel(logging.INFO)

Log Levels

  • INFO - Logs all retry attempts
  • ERROR - Logs only when giving up
  • WARNING - Custom level for important retries
  • DEBUG - Detailed information
# Only log when giving up
logging.getLogger("backoff").setLevel(logging.ERROR)

# Log all retries
logging.getLogger("backoff").setLevel(logging.INFO)

Custom Logger

Specify a custom logger by name or instance.

Logger by Name

@backoff.on_exception(
    backoff.expo,
    Exception,
    logger="my_custom_logger",
)
def my_function():
    pass

Logger Instance

import logging

my_logger = logging.getLogger("my_app.retries")
my_logger.addHandler(logging.FileHandler("retries.log"))
my_logger.setLevel(logging.WARNING)


@backoff.on_exception(
    backoff.expo,
    Exception,
    logger=my_logger,
)
def my_function():
    pass

Disable Logging

Pass logger=None to disable all default logging:

@backoff.on_exception(backoff.expo, Exception, logger=None)
def my_function():
    pass

Use with custom event handlers for complete control:

def my_custom_log(details):
    print(f"Custom log: {details}")


@backoff.on_exception(
    backoff.expo,
    Exception,
    logger=None,
    on_backoff=my_custom_log,
)
def my_function():
    pass

Formatting

Basic Format

import logging

logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    level=logging.INFO,
)

logging.getLogger("backoff").addHandler(logging.StreamHandler())

Structured Logging (JSON)

import logging
import json


class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        return json.dumps(log_data)


handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())

backoff_logger = logging.getLogger("backoff")
backoff_logger.addHandler(handler)
backoff_logger.setLevel(logging.INFO)

Multiple Handlers

Send logs to multiple destinations:

import logging

backoff_logger = logging.getLogger("backoff")

# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)

# File handler
file_handler = logging.FileHandler("backoff.log")
file_handler.setLevel(logging.INFO)

# Add both handlers
backoff_logger.addHandler(console_handler)
backoff_logger.addHandler(file_handler)
backoff_logger.setLevel(logging.INFO)

Per-Function Logging

Use different loggers for different functions:

critical_logger = logging.getLogger("critical_ops")
routine_logger = logging.getLogger("routine_ops")


@backoff.on_exception(
    backoff.expo,
    Exception,
    logger=critical_logger,
    max_tries=10,
)
def critical_operation():
    pass


@backoff.on_exception(
    backoff.expo,
    Exception,
    logger=routine_logger,
    max_tries=3,
)
def routine_operation():
    pass

Complete Example

import logging
from logging.handlers import RotatingFileHandler

# Create custom logger
logger = logging.getLogger("myapp.backoff")
logger.setLevel(logging.INFO)

# Console handler with WARNING level
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
console_format = logging.Formatter("%(levelname)s: %(message)s")
console_handler.setFormatter(console_format)

# File handler with INFO level and rotation
file_handler = RotatingFileHandler(
    "backoff.log",
    maxBytes=10 * 1024 * 1024,  # 10MB
    backupCount=5,
)
file_handler.setLevel(logging.INFO)
file_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(file_format)

# Add handlers
logger.addHandler(console_handler)
logger.addHandler(file_handler)


# Use in decorator
@backoff.on_exception(
    backoff.expo,
    Exception,
    logger=logger,
    max_tries=5,
)
def my_function():
    pass