0

I'm trying to generate a bunch of functions based on the respective methods of a logging.Logger instance from inside a function scope. The functions/methods I am interested in are:

['debug', 'info', 'warning', 'error', 'critical', 'exception']

which all share the same prototype.

Now I can obviously state:

global debug, info, warning, error critical, exception

and then go ahead and def each of them. I checked it and that works fine. Problem is that I have to repeat myself.

So I have an instance of a logging.Logger named mylog which is already a global. The following does what I want, but by hardcoding the name of the function/method respectively:

global debug
def debug(msg, *args, **kwargs):
    if mylog: mylog.debug(msg, *args, **kwargs)

For the sake of brevity I'm going to write my examples all with only debug in mind, skipping the rest. Unfortunately the real code is somewhat more complex so that the requirements for using a lambda don't seem to be fulfilled (single statement, right?).

So my obvious thinking was to use getattr with the name of "debug" on mylog and simply call whatever I get back inside the defined function and add that by name to the globals() dict. Example:

for fct in ['debug']:
    def x(msg, *args, **kwargs):
        if mylog: getattr(mylog, fct)(msg, *args, **kwargs)
    globals()[fct] = x

But I think this is not actually legal as I am calling what's supposed to be a class method as if it were a static method. Nevertheless this "works", but each output line is followed by another line saying: None.

So I thought to myself that I could possibly pass the instance as the first parameter. After all that's what the self is all about:

for fct in ['debug']:
    def x(msg, *args, **kwargs):
        if mylog: getattr(mylog, fct)(mylog, msg, *args, **kwargs)
    globals()[fct] = x

which gives me:

TypeError: not all arguments converted during string formatting

which is the identical effect that I get from this:

for fct in ['debug']:
    import types
    def x(msg, *args, **kwargs):
        if mylog: types.MethodType(getattr(mylog, fct), mylog)(msg, *args, **kwargs)
    globals()[fct] = x

which I based on the accepted answer of this question: Dynamically bind method to class instance in python

Applying the answer from Alex Martelli here gives the same error.

What am I doing wrong? How can I achieve the desired behavior without having to repeat myself?


Full traceback:

Traceback (most recent call last):
  File "/usr/lib64/python2.6/logging/__init__.py", line 776, in emit
    msg = self.format(record)
  File "/usr/lib64/python2.6/logging/__init__.py", line 654, in format
    return fmt.format(record)
  File "/usr/lib64/python2.6/logging/__init__.py", line 436, in format
    record.message = record.getMessage()
  File "/usr/lib64/python2.6/logging/__init__.py", line 306, in getMessage
    msg = msg % self.args
TypeError: not all arguments converted during string formatting

Complete script, based on the very first example code above:

#!/usr/bin/env python
import sys, logging

def setup_logging():
    global mylog
    mylog = logging.getLogger('foobar')
    mylog.addHandler(logging.StreamHandler())
    for fct in ['debug']:
        def x(msg, *args, **kwargs):
            if mylog: getattr(mylog, fct)(mylog, msg, *args, **kwargs)
        globals()[fct] = x

def main():
    setup_logging()
    debug('TEST LOG MESSAGE')

if __name__ == '__main__':
    sys.exit(main())
Community
  • 1
  • 1
0xC0000022L
  • 18,189
  • 7
  • 69
  • 131

1 Answers1

1

It seems to me that this ought to work:

debug = mylog.debug

or:

debug, info, warning, error, critical, exception = (
    mylog.debug, mylog.info, mylog.warning, 
    mylog.error, mylog.critical, mylog.exception)

To set it dynamically:

for fct in ('debug', 'info', 'warning'):
    globals()[fct] = getattr(mylog, fct)

Your for loops for fct in ('debug'): are iterating over the string 'debug', you would have needed a comma for fct in ('debug',): to make it a tuple.

Also note that a construct such as:

for fct in ['debug']:
    def x(msg, *args, **kwargs):
        if mylog: getattr(mylog, fct)(msg, *args, **kwargs)
    globals()[fct] = x

defines a function uses the current value of `fct`` when it is called. You need an extra level of indirection to handle more than one logging method. e.g.

def make_log(fct):
    def x(msg, *args, **kwargs):
        if mylog: getattr(mylog, fct)(msg, *args, **kwargs)
    return x

for fct in ['debug','info']:
    globals()[fct] = make_log(fct)
Duncan
  • 79,697
  • 10
  • 108
  • 148
  • thanks for your time. This dodges the question, though, how to do it based on the name (or "dynamically"). But it *is* less verbose, which is also cool. – 0xC0000022L Oct 28 '14 at 15:18
  • OMG. I just realized. My own example used several entries. A list also works or `('debug', )`. Trying your method now. Corrected the examples in my question. – 0xC0000022L Oct 28 '14 at 15:28
  • the way you're setting it dynamically works, of course. But doesn't take into account that I need some extra stuff in the code, which was represented by the `if mylog:` in my question. Still +1 for all your efforts. – 0xC0000022L Oct 28 '14 at 15:34
  • awesome, thanks. it was the fact that I didn't use another level of abstraction that tripped me up, from how I understand it. – 0xC0000022L Oct 28 '14 at 16:43