[Python-Dev] PEP282 and the warnings framework

Vinay Sajip vinay_sajip@red-dove.com
Thu, 16 May 2002 16:33:06 +0100

> The standard module could have the following:
> class ClassFilter(Filter):
>      def __init__(self, classes, name="")
>          super(ClassFilter, self).__init__(name)
>          self.classes = classes
>      def filter(self, record):
>          return isinstance(record, self.classes)

> then you could use this filter.
> It should be possible to derive from your class LogRecord,
> formatting of the message should not be done by Fomatter.format()
> which does a record.message = record.msg % record.args, but should
> be the responsibility of LogRecord.__str__(), the default implementation
> could simply have
>      def __str__(self):
>          return self.msg % self.args

The last part (about __str__) is reasonable. But I disagree with the
statement that the LogRecord should do formatting. Remember - logging is
used by lots of people in lots of scenarios. For example, I have used it in
such a way that messages to stderr/stdout do not have timestamps (e.g. for
warning/error messages, timestamps appear a little loud), but information
sent to file *does* have timestamps. Likewise, I'm sometimes interested in
threads, sometimes not. I'd like to change the formatting behaviour of a
long-running program, while it's running. Ideally, I might choose to only
change certain handlers, e.g. the FileHandlers. The point here is that you
should be able to change these sorts of things *just* by changing the
logging configuration. You can't do this effectively if the basic decision
making is devolved to lots of user-defined classes in an arbitrary way - you
would need to either change source code, or have configurability of these
user-defined classes. To me, this is an undesirable outcome.

> The constructor for LogRecord should be lightweight enough, so that it
> is convenient to create LogRecord instances and pass them to Logger.log
> etc. i.e. determining file name and line number should not be the
> responsiblity of the function that creates the LogRecord instance,
> but the LogRecord constructor.

As it happens, it's done in the Logger in logging.py. LogRecord is kept
fairly minimal; it's just a handy place to keep attributes of the event.

> The type of the message/log record is simply another property I
> can use to filter. And I can extend my filter criteria, because
> I'm not tied to the LogRecord instance that gets implicitely
> created by Logger.makeRecord. Yes, Logger.makeRecord is documented
> as a "factory method", but I couldn't find any possibility to replace
> the factory.

You can subclass Logger and redefine makeRecord. Then use
logging.setLoggerClass to ask the logging system to instantiate your own
logger class. Or, if you really want to take a shortcut, you can bind
Logger.makeRecord to a function of your own devising by a simple assignment.

import logging

class MyLogRecord(logging.LogRecord):
    def __init__(self, name, lvl, fn, lno, msg, args, exc_info):
        logging.LogRecord.__init__(self, name, lvl, fn, lno, msg, args,
        self.msg += "(MyLogRecord)"

def myMakeRecord(self, name, lvl, fn, lno, msg, args, exc_info):
    return MyLogRecord(name, lvl, fn, lno, msg, args, exc_info)

logging.Logger.makeRecord = myMakeRecord

root = logging.getLogger("")

> >>> FWIW, once you allow logging 'string'-type messages, most logged
> >>> messages will be a string (especially debug messages), because it is
> >>> much easier than creating an instance of some other clsas.  Thus, if
> >>> your categorization is based on the class of the logged message, the
> >>> "string" category gets very large...
> So what is different from the current implementation where all messages
> are LogRecord instances?

It's a stylistic difference. In one case you pass strings into the logging
API; in the other, you pass instances. The question is, what's most
convenient for most users most of the time? To my way of thinking, strings
serve in nearly all cases and are simpler. If you *want* classes, you can
utilize them using the existing functionality; there's no need to force
*everyone* to use classes.


Vinay Sajip