How to Build a Sidecar Logging Proxy in Python
Wrap any object with a proxy that transparently logs every method call, arguments, return value, and execution time to a file — mimicking a sidecar pattern.
Python code
46 linesimport logging
import time
from datetime import datetime
class LoggingProxy:
"""Sidecar-style proxy that logs all calls to a wrapped object."""
def __init__(self, target, log_file="proxy.log"):
self._target = target
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format="%(asctime)s - %(message)s",
)
self.logger = logging.getLogger("proxy_sidecar")
def __getattr__(self, name):
attr = getattr(self._target, name)
if callable(attr):
def wrapper(*args, **kwargs):
start = time.time()
self.logger.info(f"CALL {name} args={args} kwargs={kwargs}")
result = attr(*args, **kwargs)
elapsed = time.time() - start
self.logger.info(f"RETURN {name} result={result} elapsed={elapsed:.4f}s")
return result
return wrapper
return attr
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
if __name__ == "__main__":
calc = Calculator()
proxy = LoggingProxy(calc, "proxy.log")
print(f"add(2, 3) = {proxy.add(2, 3)}")
print(f"multiply(4, 5) = {proxy.multiply(4, 5)}")
print("Sidecar log written to proxy.log")
Output
add(2, 3) = 5
multiply(4, 5) = 20
Sidecar log written to proxy.log
The contents of proxy.log will look like (timestamps vary):
2025-01-01 10:00:00,123 - CALL add args=(2, 3) kwargs={}
2025-01-01 10:00:00,124 - RETURN add result=5 elapsed=0.0010s
2025-01-01 10:00:00,125 - CALL multiply args=(4, 5) kwargs={}
2025-01-01 10:00:00,126 - RETURN multiply result=20 elapsed=0.0010s
How it works
The LoggingProxy uses __getattr__ to intercept attribute access on the wrapped object. When a callable is requested, it returns a wrapper that logs the call before and after execution, capturing timing with time.time(). The logging module writes formatted entries to a file, emulating a sidecar that observes traffic without modifying the original object. This keeps concerns separated — the target stays pure while observability lives in the proxy.
Common mistakes
- Forgetting to call `time.time()` before and after the actual call — measuring only the wrapper overhead skews latency.
- Using the same logger across multiple proxies without a unique name, causing interleaved logs.
- Not handling exceptions in the wrapper — crashes skip the RETURN log and lose timing data.
Variations
- Add exception logging with a `try/except` inside the wrapper to record failures.
- Use a context manager or decorator on individual methods instead of a generic proxy.
Real-world use cases
- Providing sidecar-style observability for legacy services without changing their code, by wrapping internal API clients.
- Auditing database access in a microservice by proxying the ORM session and logging every query with latency.
- Debugging third-party library calls in production by logging arguments and return values to diagnose integration issues.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.