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.

Medium Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

46 lines
Python 3.9+
import 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

stdout
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

  1. Add exception logging with a `try/except` inside the wrapper to record failures.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.