Question

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)
Was it helpful?

Solution 2

You cannot easily change this. The goal of the logging module funcName is to report exact locations of the source code line, not the function it represents. The idea is that you use it in combination with the lineno and filename entries to pinpoint the source code, not what function was called.

In order to achieve this, the log module uses code object introspection to determine the real function name:

def findCaller(self):
    """
    Find the stack frame of the caller so that we can note the source
    file name, line number and function name.
    """
    f = currentframe()
    #On some versions of IronPython, currentframe() returns None if
    #IronPython isn't run with -X:Frames.
    if f is not None:
        f = f.f_back
    rv = "(unknown file)", 0, "(unknown function)"
    while hasattr(f, "f_code"):
        co = f.f_code
        filename = os.path.normcase(co.co_filename)
        if filename == _srcfile:
            f = f.f_back
            continue
        rv = (co.co_filename, f.f_lineno, co.co_name)
        break
    return rv

Short of reconstructing the _decorador code object you cannot alter what is reported here. Reconstructing the code object can be done; you could build a facade function with exec that calls the decorator, for example. But for this to work with a closure is more work than you should worry about, really.

I'd instead include the function name of the wrapped function:

logging.debug('This message should go to the log file (debug) (function %r)', 
              funcion)

OTHER TIPS

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.

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

Tim's answer worked for me but I didnt need to use the middle wrapper:

def log_and_call(func, statement):
        caller = getframeinfo(stack()[1][0])
        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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top