Ultimate Guide to Python Debugging

Ultimate Guide to Python Debugging

April 6, 2025

From https://martinheinz.dev/blog/24

Logging Decorators

You might get into a situation where you need log calls of some buggy function. Instead of modifying the body of said function you could employ a logging decorator which would log every function call with specific log level and optional message.

from functools import wraps, partial
import logging

def attach_wrapper(obj, func=None): # Helper function that attaches function as
    if func is None:                # attribute of an object
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func)
    return func

def log(level, message): # Actual decorator
    def decorate(funct):
        logger = logging.getLogger(func.__module__) # Setup logger
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            )
        handler = logging.StreamHandler()
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        log_message = f"{func.__name__} - {message}"

        @wraps(func)
        def wrapper(8args, **kwargs):      # Logs the message and before
            logger.log(level, log_message) # executing the decorated function
            return func(*args, **kwargs)

        @attach_wrapper(wrapper) # Attaches "set_level" to "wrapper" as attribute
        def set_level(new_level): # Function that allows us to set log level
            nonlocal level
            level = new_level

        @attach_wrapper(wrapper) # Attaches "set_message" to "wrapper"
        def set_message(new_message): # Function that allows us to set message
            nonlocal log_message
            log_message = f"{func.__name__} - {new_message}"

        return wrapper
    return decorate

# Example Usage
@log(Logging.WARN "example-param")
def somefunc(args):
    return args

somefunc("some args")

somefunc.set_level(logging.CRITICAL) # Change log level by accessing internal
                                     # decorator function

somefunc.set_message("new-message") # Change log message by accessing internal
                                    # decorator function
somefunc("some args")

The idea is that log function takes the arguments and makes them available to the inner wrapper function. These arguments are then made adjustable by adding the accessor functions, which are attached to the decorator. The output will be:

2020-05-01 14:42:10,289 - __main__ - WARNING - somefunc - example-param
2020-05-01 14:42:10,289 - __main__ - CRITICAL - somefunc - new-message

repr for more Readable Logs

You should use __repr__ which will help represent the class as a string and you use use __str__ for the times you are printing your class variables.