OSDir


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

logging module - how to include method's class name when using %(funcName)


Malcolm Greene wrote:

> I'm using the Python logging module and looking for a way to include a
> method's class name when using %(funcName). Is this possible? When you
> have several related classes, just getting the function (method) name is
> not enough information to provide context on the code being executed.
> I'm outputting module name and line number so I can always go back and
> double check a caller's location in source, but that seems like an
> archaic way to find this type of information.

In the code below I took the same approach, but automated it with pyclbr.
While I'm not really happy with the result (is inspect or ast more suitable, 
should Logger be subclassed or adapted rather than specifying the LogRecord 
factory, can the class be determined lazily) here's the current state of 
things:

$ cat findclass.py
def ham(): log.warning("inside ham function")
import bisect
import pyclbr
import logging

LogRecord = logging.LogRecord


class Module:
    def __init__(self, module):
        mod = pyclbr.readmodule_ex(module)
        line2func = []

        for classname, cls in mod.items():
            if isinstance(cls, pyclbr.Function):
                line2func.append((cls.lineno, "<no-class>", cls.name))
            else:
                for methodname, start in cls.methods.items():
                    line2func.append((start, classname, methodname))

        line2func.sort()
        keys = [item[0] for item in line2func]
        self.line2func = line2func
        self.keys = keys

    def line_to_class(self, lineno):
        index = bisect.bisect(self.keys, lineno) - 1
        return self.line2func[index][1]


def lookup_module(module):
    return Module(module)


def lookup_class(module, funcname, lineno):
    if funcname == "<module>":
        return "<no-class>"

    module = lookup_module(module)
    return module.line_to_class(lineno)


def makeLogRecord(*args, **kw):
    record = LogRecord(*args, **kw)
    record.className = lookup_class(
        record.module, record.funcName, record.lineno
    )
    if record.className != "<no-class>":
        record.funcName = "{}.{}".format(record.className, record.funcName)
    return record


logging.setLogRecordFactory(makeLogRecord)

logging.basicConfig(
    format=logging.BASIC_FORMAT
    + " class=%(className)s func=%(funcName)s lineno=%(lineno)s"
)

log = logging.getLogger()
log.warning("module-level")


def foo():
    log.warning("inside foo function")
def bar(): log.warning("inside bar function")


class A:
    def foo(self):
        log.warning("inside A.foo method")


class B:
    def foo(self):
        log.warning("inside B.foo method")
A().foo()
B().foo()
foo()
bar()
ham()
$ python3.7 findclass.py
WARNING:root:module-level class=<no-class> func=<module> lineno=61
WARNING:root:inside A.foo method class=A func=A.foo lineno=71
WARNING:root:inside B.foo method class=B func=B.foo lineno=76
WARNING:root:inside foo function class=<no-class> func=foo lineno=65
WARNING:root:inside bar function class=<no-class> func=bar lineno=66
WARNING:root:inside ham function class=<no-class> func=ham lineno=1