[Python-Dev] PEP282 and the warnings framework

Vinay Sajip vinay_sajip@yahoo.co.uk
Fri, 17 May 2002 20:00:00 +0100


> this is an interesting point. LogRecord is the basic unit of the current
logging.
> But you can not pass customized (subclassed) LogRecords without them
> beeing wrapped (read: contained) in another LogRecord instance. Thus
> you have to e.g. access 'record.msg' in filters.  Why two instances
> where one is enough, more flexible and pretty straightforward?

There aren't two instances. At the moment, you pass in a level, a string and
some optional arguments. These get parcelled up into a LogRecord which is
passed between loggers, handlers and filters. At present the LogRecord is an
internal class only; this debate is (at least partly) about whether the
class should be available for public use.

> In addition you cannot currently do *early* filtering based
> on anything else than 'severity'. I find that strange considering
> that the logging-api would hardly loose its convenience (warn/error/...)

You've got it the wrong way round; severity is *defined* as a measure of
importance the developer assigns to the event. It's not imposed by the
logging system, merely used by it when dispatching the event. The logging
system merely constrains these measures to be integers, and provides some
commonly used integer severity values with friendly names, and with
convenience methods and builtin filtering mechanisms for them. Integer
values, besides being easy to use, offer less processing overhead.

The prevalent use and popularity of log4j and similar systems underlines the
popularity of integer based severities; many man-years of trial and error
and practical experience have gone into their current design, and many good
brains have worked on them. (But please feel free to point to other systems
in widespread use which use your kind of approach, so that I can look into
what practical experience has shown *their* implementors.)

If you want to use criteria other than numeric severities, logging.py makes
this possible for you. It does not make this as convenient as possible for
you at the expense of a simpler interface for everyone else. If you are
arguing for the *default* filtering mechanism to be something other than
integer levels, I am not sure I have the inclination to rewrite the PEP and
the implementation to accommodate what seems to be a minority view.

> if you made LogRecord or better logging.Event the basic working unit.
> Early severity checking could still be done in these methods. The
> log-method though should work with Events & subjects (and a default
> factory if the first object isn't already an Event).

I don't see how the checking could get any earlier.The first thing checked
by a logger is the severity of the event against the severity threshold
configured for that logger. Any earlier, and you're in your own code
deciding whether to call the logging API or not. If you need to find what
the severity of a logger is before you call a logging method on it, there's
always getEffectiveLevel().

Thinking about this thread, there are two strands: the first talks about
changing the interface - the method signatures - to the logging API. The
second comes from wanting to be able to pass other, arbitrary state into an
event, which can perhaps be used by user-defined loggers, handlers and
filters. To me, the second strand is valid; the first is not, as it relates
not to functional requirements, but esthetics and personal preferences.

Looking at the second strand, there are two approaches which spring to mind:
allow users to inherit from LogRecord, and pass that in to the logging API,
or allow them to pass some other arbitrary instance into a logging call,
which they can query in their own subclasses of Logger/Handler/Filter. With
the second approach, the passed in instance becomes an attribute of the
LogRecord for that event.

It might appear that allowing subclasses of LogRecord is the more natural
thing to do. But this has the potential to stop me from extending
LogRecord's functionality in the future, as some new attribute I introduce
might conflict with the same-named attribute in some user's
LogRecord-derived class. Since I want to preserve this freedom for myself
and other maintainers of logging, the best option seems to be: allow another
instance of an arbitrary class to hold whatever additional information the
caller wants.

Next question: how to pass in this information? two choices spring to mind:

1. Use a keyword argument, extra_info=None. You can use this on any call
such as warn(), debug() etc. just as exc_info is currently used. A passed in
value would become the extra_info attribute of the LogRecord.

2. Use the "msg" parameter, which all logging calls already have, to have
the semantics such that it is either a format string, or an instance such
that str(msg) returns the desired format string. For the simple case, just
pass in a string. For the more complex case, pass an instance - the caller
can put any state in there that they want. It will not be used by the core
logging module, except to call __str__() on it for the format string. But it
can be used by user-derived classes, which will find it in the msg attribute
of the LogRecord. This might seem "hackish" to some, but it's not really -
it's just a question of packaging.

Don't want multiple logging channels? Easy - just use the root logger. Don't
want to use integer-based severities? Easy. Just never set any severity on
any logger, and implement your own dispatch logic in a handler or filter,
and call the logger with any severity. After a quick level check, which will
succeed, any filter you configure for a logger will be called. There, you
can do your own thing. You have access to both the LogRecord and your own
additional instance which implements whatever behaviour you want.

Slightly OT: There's a lot of people who think that the logging system
should be a completely generalized system for event generation and
processing. I've shied away from using a word like "Event", preferring
"LogRecord" which tries to indicate that this is a focused class for a
specific job. Some of what you want seems to point in the same direction,
though that may not be your intention. I disagree with this - the logging
system should be a simple system which allows developers, both novice and
experienced, to flag events of interest in an application, and for different
audiences for those events (whether developers themselves, support staff or
end users) to decide which of the events they want to see. Developers get to
say "what happened" (message/args), "where it happened" (logger name), "when
it happened" (whenever the logging call is made) and "how important" (as a
numeric level). The audiences (or their mediator who configures the logging
system) get to decide which of of the audiences gets to see events for
particular values of "where" and "how important". The wider ranging event
generation system has its place, but I don't think that place is in the core
Python distribution. Maybe in Python 3000 :-)

Regards

Vinay



_________________________________________________________
Do You Yahoo!?
Get your free @yahoo.com address at http://mail.yahoo.com