[Python-Dev] PEP282 and the warnings framework

Walter Dörwald walter@livinglogic.de
Thu, 16 May 2002 20:36:08 +0200

Vinay Sajip wrote:

 > [...]
>>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.

I meant only the formatting of the message itself, i.e. the
"self.msg % self.args" part. Timestamps, file/line info etc. should
be the responsiblity of the Logger/Formatter.

 > 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.

You're right, configuration should be changable at runtime. Doing this
when configuration involves custom classes is hard, but this shouldn't
be your job, but the job of those that implement these custom classes.

>>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.

I'd say it *is* 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. 

Yes, that's the purpose of the factory method, but then I have to
replace the factory method that generates the factory ...

> Then use
> logging.setLoggerClass to ask the logging system to instantiate your own
> logger class. 

... which is done here. But this would change the LogRecord classes
that are used on a global scale. I'd like to decide which class should
be used when I'm creating the event. BTW, in setLoggerClass() you should
probably use
     if not issubclass(klass, Logger):
instead of
     if not (Logger in klass.__bases__):

> 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.
> Quick'n'dirty:

Exactly. This work more out of accident, then out of design.

> [...]

>>>>>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.

What I want is something like this (similar to what the warning
framework does):

def log(event, eventtype=LogRecord):
     if not isinstance(event, LogRecord):
         event = eventtype(event)
     ... the rest of the code

This way you can use simple strings:
     log("something wonderful has happened")
you can use string/type combinations:
     log("something wonderful has happened", logging.CriticalLogRecord)
and you can use instances:
     log(logging.CriticalLogRecord("something wonderful has happened"))

I you really want the keep the severity out of the class,
we could do this:

def log(event, level=INFO, eventtype=LogRecord):
     if not isinstance(event, LogRecord):
         event = eventtype(event, level)
     ... the rest of the code

    Walter Dörwald