Using Decorators for Python Logging

Oct 11, 2024

In Python, when working on code—whether it's a script or an application—logging variables used in functions is essential for debugging, troubleshooting, and tracing.

The cleanest and most minimal solution for this is to use decorators.

Introduction to Decorators

Decorators dynamically alter the functionality of a function, method, or class without directly modifying the source code. We’ll show how to implement logging for function variables using decorators.

We start by importing the logging module and initializing a logger with the module name (__name__):

import logging
import sys

logger = logging.getLogger(__name__)

Setting Up the Logger

Next, we set up the logger, configure the logging level, and format the output to show relevant details:

import logging
import sys

logger = logging.getLogger()

def init_logger(logger):
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.DEBUG)

    formatter = logging.Formatter("[%(asctime)s][file:%(filename)s] %(levelname)s %(message)s")
    handler.setFormatter(formatter)

    logger.addHandler(handler)

Implementing the log_content Decorator Now, we create a log_content class decorator that tracks local variables and logs their names and values during the execution of a function:

class log_content(object):
    def __init__(self, func):
        init_logger(logger)
        self._locals = {}
        self.func = func
        self.name = func.__name__

    def __call__(self, *args, **kwargs):
        def tracer(frame, event, arg):
            if event == "return":
                self._locals = frame.f_locals.copy()

        # Tracer is activated on the next call, return, or exception
        sys.setprofile(tracer)

        try:
            # Trace the function call
            res = self.func(*args, **kwargs)
        finally:
            # Disable tracer and restore the old one
            sys.setprofile(None)

        print(self._locals)

        for variable, value in self._locals.items():
            logger.debug("[@%s] variable %s = %s", self.name, variable, value)

        return res

    def clear_locals(self):
        self._locals = {}

    @property
    def locals(self):
        return self._locals

Usage Example Here’s how you can use the log_content decorator in a Python function:

@log_content
def sample_function(x, y):
    z = x + y
    return z

When sample_function is called, it will log the values of the local variables x, y, and z.

Mohamed Guirat