'How get the called function to loggin with decorators?

I want to write to a log file some events. In order to do this I've used functions decorators to add the loggin code, and report the function called. But, the output is always the same function, the decorator function _decorador.

I'm using the %(funcName)s parameter in format logging.basicConfig

Output in example.log:

04/21/2014 09:32:41 AM DEBUG This message should go to the log file _decorador
04/21/2014 09:32:41 AM INFO So should this _decorador
04/21/2014 09:32:41 AM WARNING And this, too _decorador
04/21/2014 10:46:23 AM DEBUG This message should go to the log file (debug) _decorador
04/21/2014 10:46:23 AM INFO So should this (info) _decorador
04/21/2014 10:46:23 AM WARNING And this, too (warning) _decorador

Desired output in example.log:

04/21/2014 09:32:41 AM DEBUG This message should go to the log file mi_funcion
04/21/2014 09:32:41 AM INFO So should this mi_funcion
04/21/2014 09:32:41 AM WARNING And this, too mi_funcion
04/21/2014 10:46:23 AM DEBUG This message should go to the log file (debug) mi_funcion
04/21/2014 10:46:23 AM INFO So should this (info) mi_funcion
04/21/2014 10:46:23 AM WARNING And this, too (warning) mi_funcion

My code:

#!usr/bin/python3
# -*- coding: UTF-8 -*-

import logging

FORMAT = '%(asctime)s %(levelname)s %(message)s %(funcName)s'
logging.basicConfig(filename='example.log', level=logging.DEBUG, format=FORMAT, datefmt='%m/%d/%Y %I:%M:%S %p')

# Decorator function, writes in the log file.
def decorador(funcion):
    def _decorador(*args, **kwargs):
        funcion(*args, **kwargs)
        logging.debug('This message should go to the log file (debug)')
        logging.info('So should this (info)')
        logging.warning('And this, too (warning)')
    return _decorador

    @decorador
def mi_funcion(arg1, arg2):
    print("Code asset: %s; Registry number: s%" % (arg1, arg2))

mi_funcion("18560K", 12405)


Solution 1:[1]

You can extract the function name from the funcion object:

def decorador(funcion):
    def _decorador(*args, **kwargs):
        funcion(*args, **kwargs)
        logging.debug('This message should go to the log file (debug) %s',
                funcion.__name__)
        # ...
    return _decorador

I get this output after running the modified code:

cat example.log
04/21/2014 11:37:12 AM DEBUG This message should go to the log file (debug) mi_funcion

Solution 2:[2]

It's 2022 and this is still difficult.

Here's a complete example adapted from Using functools.wraps with a logging decorator

from inspect import getframeinfo, stack
import logging
from functools import wraps

class CustomFormatter(logging.Formatter):
    """Custom formatter, overrides funcName with value of name_override if it exists"""
    def format(self, record):
        if hasattr(record, 'name_override'):
            record.funcName = record.name_override
        if hasattr(record, 'file_override'):
            record.filename = record.file_override
        if hasattr(record, 'line_override'):
            record.lineno= record.line_override
        return super(CustomFormatter, self).format(record)

# setup logger and handler
logger = logging.getLogger(__file__)
handler = logging.StreamHandler()
logger.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG)
handler.setFormatter(CustomFormatter('%(asctime)s - %(filename)s:%(lineno)s - %(funcName)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

def log_and_call(statement):
    def decorator(func):
        caller = getframeinfo(stack()[1][0])
        @wraps(func)
        def wrapper(*args, **kwargs):
            # set name_override to func.__name__
            logger.info(statement, extra={
                'name_override': func.__name__,
                'file_override': os.path.basename(caller.filename),
                'line_override': caller.lineno
                })
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_and_call("This should be logged by 'decorated_function'")
def decorated_function():  # <- the logging in the wrapped function will point to/log this line for lineno.
    logger.info('I ran')

decorated_function()

Defining the caller outside of the wrapper function will correctly get the calling function's (i.e. the wrapped function) filename and line number.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Tim