How to Build a Simple Decorator That Logs Function Calls in Python

This code shows how to create a reusable decorator that logs each function call, including arguments, return value, and execution time.

Easy Python 3.9+ Aug 9, 2026 Functions & basics 11 views 0 copies

Python code

26 lines
Python 3.9+
import functools
import time

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} returned {result} in {end - start:.4f}s")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

@log_calls
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

if __name__ == "__main__":
    add(2, 3)
    greet("Alice")
    greet("Bob", greeting="Hi")

Output

stdout
Calling add with args=(2, 3), kwargs={}
add returned 5 in 0.0000s
Calling greet with args=('Alice',), kwargs={}
greet returned Hello, Alice! in 0.0000s
Calling greet with args=('Bob',), kwargs={'greeting': 'Hi'}
greet returned Hi, Bob! in 0.0000s

How it works

The log_calls decorator wraps the original function with a wrapper that logs before and after the call. functools.wraps copies metadata like __name__ from the original function so debugging stays clean. The wrapper accepts *args and **kwargs to handle any function signature. Timing uses time.time() around the actual call to measure execution. The decorator returns the original result so behavior remains unchanged.

Common mistakes

  • Forgetting `functools.wraps`, which breaks introspection and `__name__` on the decorated function.
  • Not returning the result from the wrapper, silently swallowing the function's return value.
  • Hardcoding argument names instead of using `*args` and `**kwargs` for generality.

Variations

  1. Use `functools.partial` or a decorator factory to accept extra options like log level.
  2. Write the logs to a file or `logging` module instead of `print` for production use.

Real-world use cases

  • Adding lightweight logging to API endpoints to track incoming requests and response times.
  • Instrumenting database queries to spot slow calls in a batch processing script.
  • Debugging a complex algorithm by tracing input/output for each recursive call during development.

Sponsored

Run this sample

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

Open editor

More from Functions & basics

Related tutorials and quizzes for this topic.