tags:

views:

295

answers:

5

When I first learned Python, I got used to doing this:

  print "text", lineNumber, "some dictionary", my_dict

When I wrote my own logging facility, I naturally wanted to be able to hand it an arbitrarily-sized list of items, so I did this:

def error(*args):
   print ERR_PREFIX,
   for _x in args:
      print _x,
   print "\r\n",

error("text", lineNumber, "some dictionary", my_dict)

Now I want to start using the logging package because it has a lot more goodies and I don't feel like replicating their effort. Overall it looks like a clean design that can do a lot. But I'm stymied by the fact that you can no longer present it with the same list of items for it to print. Instead, I must change all my calls to something more like this:

error("text %d some dictionary %s" % (lineNumber, my_dict))

Or, I could do something really silly like this:

error(' '.join(map, str(("text", lineNumber, "some dictionary", my_dict))))

The question is, why omit such an obvious usage case? If you want to go from the typical 'print' statement straight to the new-fangled logging facility, shouldn't this be easier?

As a follow-up question, can you think of a way to override the Logger class to perform this?

A: 

Well, printing and logging are two very different things. It stands to reason that as such their usages could potentially be quite different as well.

Andrew Hare
A: 

It's relatively easy to add a method to a class dynamically. Why not just add your method to Logging.

Charlie Martin
+4  A: 

I would suggest that it would be better to update the existing logging messages to the style that the logging module expects as it will be easier for other people looking at your code as the logging module will not longer function as they expect.

That out of the way, the following code will make the logging module behave as you desire.

import logging
import types

class ExtendedLogRecord(logging.LogRecord):

    def getMessage(self):
        """
        Return the message for this LogRecord.

        Return the message for this LogRecord after merging any user-supplied
        arguments with the message.
        """
        if not hasattr(types, "UnicodeType"): #if no unicode support...
            msg = str(self.msg)
        else:
            try:
                msg = str(self.msg)
            except UnicodeError:
                msg = self.msg      #Defer encoding till later
        if self.args:
            msg +=' '+' '.join(map(str,self.args))
        return msg

#Patch the logging default logging class
logging.RootLogger.makeRecord=lambda self,*args: ExtendedLogRecord(*args)

some_dict={'foo':14,'bar':15}
logging.error('text',15,'some dictionary',some_dict)

Output:

ERROR:root:text 15 some dictionary {'foo': 14, 'bar': 15}
Mark Roddy
Note that if you want to apply this to a logger other than the RootLogger, then you need to supply the self argument: log = logging.getLogger('log') log.makeRecord = lambda self, *args: ExtendedLogRecord(self, *args)
Brian
+1  A: 

Your claim about logging is not completely true.

log= logging.getLogger( "some.logger" )
log.info( "%s %d", "test", value )
log.error("text %d some dictionary %s", lineNumber, my_dict)

You don't need to explicitly use the string formatting operator, %


Edit

You can leverage your original "error" function.

def error( *args ):
    log.error( " ".join( map( str, args ) ) )

Which will probably make the transition less complex.

You can also do this.

class MyErrorMessageHandler( object ):
    def __init__( self, logger ):
        self.log= logger
    def __call__( self, *args ):
        self.log.error( " ".join( map( str, args ) ) )
error= MyErrorMessageHandler( logging.getLogger("some.name") )

Which might be palatable, also.

S.Lott
It's funny, I've been using the logging module for years without knowing this. Guess it's a case of the module being so easy to pick up that I never bothered to look at the docs where this is clearly stated.
Kiv
"You can no longer present it with the same list of items for it to print" - I believe the questioner's problem is the need to use formatting forcing him to rewrite all of his log messages, not whether or not he needs initiate the formatting himself.
Mark Roddy
+1  A: 

Patching the logging package (as one answer recommended) is actually a bad idea, because it means that other code (that you didn't write, e.g. stuff in the standard library) that calls logging.error() would no longer work correctly.

Instead, you can change your existing error() function to call logging.error() instead or print:

def error(*args):
  logging.error('%s', ' '.join(map(str, args)))

(If there might be unicode objects you'd have to be a bit more careful, but you get the idea.)