How to Build a Robust Retry Decorator with Logging in Python
This article explains how to create a reusable Python decorator that automatically retries a function a configurable number of times, supports custom exception types and delays, preserves the original function's metadata, and optionally logs each attempt with adjustable log levels.
Feature Requirements
Automatically retry a function when it fails, up to N times.
Customizable retry count via max_retries (default 3).
Specify which exception types to catch (e.g., Exception or more specific).
Optional retry interval ( delay in seconds).
Preserve the original function's metadata using functools.wraps.
Full Code Implementation
import time
from functools import wraps
def retry(max_retries=3, delay=1, exceptions=(Exception,)):
"""Retry decorator.
Args:
max_retries (int): Maximum number of retries (default 3).
delay (int or float): Seconds to wait between retries.
exceptions (tuple): Exception types to catch and retry (default all Exception).
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries <= max_retries:
try:
return func(*args, **kwargs)
except exceptions as e:
retries += 1
if retries > max_retries:
print(f"[Failed] Exceeded max retries {max_retries}, no more attempts")
raise
print(f"[Attempt {retries}/{max_retries}] Exception: {e}, retrying after {delay} seconds...")
time.sleep(delay)
return None # Should never reach here
return wrapper
return decoratorExample Usage
@retry(max_retries=5, delay=2, exceptions=(ConnectionError,))
def fetch_data():
print("Fetching data...")
# Simulate occasional failure
import random
if random.random() < 0.7:
raise ConnectionError("Connection failed, please retry")
return "Data fetched successfully"
result = fetch_data()
print(result)Sample output:
Fetching data...
[Attempt 1/5] Exception: Connection failed, please retry, retrying after 2 seconds...
Fetching data...
[Attempt 2/5] Exception: Connection failed, please retry, retrying after 2 seconds...
Fetching data...
Data fetched successfully
Enhancement: Logging on Each Retry
Record detailed logs for every retry.
Support log level control (INFO / ERROR).
Optionally write logs to console or a file.
Retry Decorator with Logging
import time
import logging
from functools import wraps
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[logging.StreamHandler()]
)
def retry(max_retries=3, delay=1, exceptions=(Exception,)):
"""Retry decorator with logging.
Args:
max_retries (int): Maximum retry attempts.
delay (int or float): Seconds between retries.
exceptions (tuple): Exception types to catch.
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries <= max_retries:
try:
logging.info(f"[{func.__name__}] Executing...")
result = func(*args, **kwargs)
logging.info(f"[{func.__name__}] Execution succeeded")
return result
except exceptions as e:
retries += 1
logging.error(f"[{func.__name__}] Retry {retries} failed: {e}")
if retries > max_retries:
logging.error(f"[{func.__name__}] Exceeded max retries {max_retries}, aborting")
raise
logging.info(f"[{func.__name__}] Waiting {delay}s before retry {retries + 1}...")
time.sleep(delay)
return None
return wrapper
return decoratorLogging‑Enabled Example
import random
@retry(max_retries=5, delay=2, exceptions=(ConnectionError,))
def fetch_data():
print("Attempting to fetch data...")
if random.random() < 0.7:
raise ConnectionError("Simulated network outage")
return "Data fetched successfully"
result = fetch_data()
print(result)Sample log output:
2025-06-29 10:30:45,123 [INFO] [fetch_data] Executing...
Attempting to fetch data...
2025-06-29 10:30:45,125 [ERROR] [fetch_data] Retry 1 failed: Simulated network outage
2025-06-29 10:30:45,126 [INFO] [fetch_data] Waiting 2s before retry 2...
2025-06-29 10:30:47,130 [ERROR] [fetch_data] Retry 2 failed: Simulated network outage
2025-06-29 10:30:47,131 [INFO] [fetch_data] Waiting 2s before retry 3...
2025-06-29 10:30:49,135 [INFO] [fetch_data] Execution succeeded
Data fetched successfullySigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
