1

Is it possible to implement the equivalent of the following (pseudo-)code in Python?

#define DEBUG(topic, msg) LOG_IMPL(Logger.DEBUG, topic, msg)
#define INFO(topic, msg) LOG_IMPL(Logger.INFO, topic, msg)
#define LOG_IMPL(level, topic, msg) if(Logger.level() <= level) { Logger.log(level, topic, msg); }

DEBUG("MyComponent", "What you logging at?")

The benefit here being you don't have to evaluate the string log messages, e.g. joining strings, calling .format(), etc.)

UPDATE:

Lazy logger message string evaluation - this answers my question so I will vote to close this post.

Community
  • 1
  • 1
Graeme
  • 4,514
  • 5
  • 43
  • 71
  • 2
    Wow, that's disgusting C++ macro with no purpose. – Puppy Jun 24 '12 at 20:13
  • What exactly are you asking? Those defines could easily be implemented in any language as functions. – Oscar Korz Jun 24 '12 at 20:13
  • @Graeme: As I understand it, you are not only asking for a nice logging module (and the python built-in is great) but also for a system that does not evaluate the log messages (if constructing them is computationally expensive) if logging is disabled. Am I mistaken? – Alex Wilson Jun 24 '12 at 20:32
  • 1
    @AlexWilson: You are correct, it's the cost of expensive log messages I am trying to avoid. – Graeme Jun 24 '12 at 21:39
  • @DeadMG: I've wrapped up the logger in my own class so I can amoung other things add some additional levels (e.g. TRACE). By calling my functions I don't benefit from the lazy evaluation of the underlying logging library (which I am using). – Graeme Jun 24 '12 at 21:41
  • @Graeme: I take it I can't tempt you with my lambda suggestion then? It does offer a fully general way of preventing any compute involved in the log message being evaluated when not required... – Alex Wilson Jun 25 '12 at 06:13
  • possible duplicate of [Lazy logger message string evaluation](http://stackoverflow.com/questions/4148790/lazy-logger-message-string-evaluation) – Graeme Jun 25 '12 at 10:03

4 Answers4

9

Python comes with batteries included, and a logging module is part of the stdlib:

from logging import getLogger

log = getLogger('my.module')

log.debug('Debug level messages')
log.warning('Warning!')
log.info('Informative message')
log.error('Error messages')
log.exception('Use this in an exception handler, the exception will be included automatically')

The above set of methods are shortcuts for the log.log(level, msg) method, which takes arbitrary (integer) levels, and the logging module defines DEBUG, WARNING and other levels.

The methods support lazy evaluation of python string formatting templates; extra arguments are interpolated only when the log level of the message actually exceeds the logging level being recorded:

log.warning('Warning message: the %s is missing %i frobnars', systemname, count)

The above message will be logged with the equivalent of 'Warning message: the %s is missing %i frobnars' % (systemname, count) only if the log message actually reaches a handler.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This makes sense but I've wrapped up the logger in my own class so I can amoung other things add some additional levels (e.g. TRACE). By calling my functions I don't benefit fomr the lazy evaluation of the underlying logging library (which I am using). – Graeme Jun 24 '12 at 21:40
  • @Graeme: When in Rome, do as the romans do. You can customize the native `logging` module to do almost anything you want. Perhaps you can use your custom class as a `Handler` which will be called by `logging` ? – ereOn Jun 25 '12 at 15:45
  • @Graeme: levels are numeric; just call `log.log(yourlevel, msg)`. `logging.DEBUG` is 10, so `TRACE = 5` would be perfectly acceptable. – Martijn Pieters Jun 25 '12 at 15:50
  • @ereOn: I have written a very light wrapper which calls into logging to format the log as I wish, add my custom levels, etc. It's a simple singleton which all my apps use and which has the 7 or so functions I require for logging - I'm not reinventing the wheel. – Graeme Jun 25 '12 at 18:14
  • 1
    @Graeme: I think you are. The logging module provides handlers and formatters, where you can format messages any way you like without subclassing the logger and loosing functionality. – Martijn Pieters Jun 25 '12 at 18:19
  • @Graeme: It always difficult to somehow "ditch" (or "not use") code you have put of efforts in because you discover too late something existed that fitted the purpose. You would probably gain a lot of maintainability by making your logging functions interface with the `logging` module. – ereOn Jun 26 '12 at 06:46
  • @Martin: I want a Logger class I can use without having to manually add for matters and handlers to it in each of my projects. I wrap up this bit of work in the constructor (encapsulation) and use a simple proxy interface. I'm not subclassing either. – Graeme Jun 26 '12 at 09:48
  • @Graeme: Well it's your call anyway. But I fail to see why you can't just put this particular initialization code in some common function and just call that function in projects that needs it. – ereOn Jun 26 '12 at 11:38
2

How about using lambdas for the messages:

log( lambda : (string1 + string2 + "%d %d" % (val1, val2)) )

And have the log function only call the function passed in if logging is enabled.

Alex Wilson
  • 6,690
  • 27
  • 44
1

Have you tried the logging module? Example:

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

Source: http://docs.python.org/howto/logging.html#logging-basic-tutorial

C0deH4cker
  • 3,959
  • 1
  • 24
  • 35
0

I came up with a solution allowing lazy evaluation of log message whilst still allowing me to encapsulate custom formatters and handlers inside a small logging proxy class.

The format string will not be evalulated unless the log message is written (logging handles this); this achieved by passing the format string and arguments seperately.

@classmethod 
def info(cls, component, msg, *args):     
    """Log an info message"""     
    cls.__log(cls.Level.INFO, component, msg, (args)  

@classmethod 
def __log(cls, level, component, msg, *args):    
    """Log a message at the requested level"""     
    logging.getLogger("local").log(level, " - ".join([component, msg.format(*args)])) 

Logger.info("MyComponent", "My message with arg '{0}'", "TestArg")
Graeme
  • 4,514
  • 5
  • 43
  • 71