
I think the log4j levels are sufficient. zLOG has more levels, but it's hard to figure out what to use them for :-). zLOG does have a nice feature for passing an exception info to a log call. I'd like to see this as a standard feature, e.g. logger.info("A normal exception occurred", exc_info=sys.exc_info()) The idea is the any of the log calls could also include a formatted traceback in the log. (I suppose this means that the Handlers need a pluggable way to format exceptions.) This is useful for all log levels. In a long running server, you may get exceptions that are unexpected but no so important that you're going to fail or bring the server down. What's the current status of this PEP? I like the general outline of stuff, but it feels a little fuzzy. That is, it seems to present a high-level description and a bunch of options. I'd like to see the concrete APIs for all of the objects. I'd also love to get at least a prototype implementation. It would be need to replace the zLOG in Zope3 with a new logging implementation that follows the PEP. Jeremy

logger.info("A normal exception occurred", exc_info=sys.exc_info())
Or you could have a call logger.exception("message") that knows to call sys.exc_info(). That would reduce the boilerplate. --Guido van Rossum (home page: http://www.python.org/~guido/)

On Wed, 20 Mar 2002 16:16:28 -0500 Guido van Rossum <guido@python.org> wrote:
logger.info("A normal exception occurred", exc_info=sys.exc_info())
Or you could have a call logger.exception("message") that knows to call sys.exc_info(). That would reduce the boilerplate.
But how would logger.exception() know what log level to use? Jeremy

Jeremy Hylton <jeremy@zope.com> wrote:
Or you could have a call logger.exception("message") that knows to call sys.exc_info(). That would reduce the boilerplate.
But how would logger.exception() know what log level to use?
Make it take an argument for the message, and another for the level? Charles -- ----------------------------------------------------------------------- Charles Cazabon <python@discworld.dyndns.org> GPL'ed software available at: http://www.qcc.ca/~charlesc/software/ -----------------------------------------------------------------------

[Jeremy Hylton wrote]
zLOG does have a nice feature for passing an exception info to a log call. I'd like to see this as a standard feature, e.g.
logger.info("A normal exception occurred", exc_info=sys.exc_info())
The idea is the any of the log calls could also include a formatted traceback in the log.
Ideas I am batting around: MarkH's and Guido's (doesn't allow controlling the level of the message): class Logger: def exception(self, msg, *args): # This is almost identical to error(), but sys.exc_info() is # called, and if available the exception details are appended to # msg. As sys.exc_info() is called, this is only useful in an # exception handler. logger.exception("Like, Oh My God!") # cannot log it at 'info' level Jeremy's: class Logger: def info(self, level, msg, *args, **kwargs): # watch for 'exc_info' in kwargs logger.info("Like, Oh My God!", exc_info=sys.exc_info()) Trent's: class Logger: def exception(self, msg, *args, **kwargs): # watch for 'level' in kwargs logger.exception("Like, Oh My God!", level=logging.INFO) Which do you all prefer?
What's the current status of this PEP? I like the general outline of stuff, but it feels a little fuzzy. That is, it seems to present a high-level description and a bunch of options. I'd like to see the concrete APIs for all of the objects.
The current status is that I have been slowly working on it in private. I agree that the current PEP is fuzzy.
I'd also love to get at least a prototype implementation. It would be need to replace the zLOG in Zope3 with a new logging implementation that follows the PEP.
Last week (I think) Vinay Sajip posted on python-list that he had a prototype implementation of PEP 282. http://www.red-dove.com/python_logging.html I like what he has and I hope to work with him (merging with what implementation scraps I have) to build the eventual proposed patch for the PEP. I haven't told him this yet though. :) Vinay, what do you think? Trent -- Trent Mick TrentM@ActiveState.com

[Jeremy Hylton wrote]
zLOG does have a nice feature for passing an exception info to a log call. I'd like to see this as a standard feature, e.g.
logger.info("A normal exception occurred", exc_info=sys.exc_info())
The idea is the any of the log calls could also include a formatted traceback in the log.
Ideas I am batting around:
MarkH's and Guido's (doesn't allow controlling the level of the message): class Logger: def exception(self, msg, *args): # This is almost identical to error(), but sys.exc_info() is # called, and if available the exception details are appended to # msg. As sys.exc_info() is called, this is only useful in an # exception handler.
logger.exception("Like, Oh My God!") # cannot log it at 'info' level
Jeremy's: class Logger: def info(self, level, msg, *args, **kwargs): # watch for 'exc_info' in kwargs
logger.info("Like, Oh My God!", exc_info=sys.exc_info())
Trent's: class Logger: def exception(self, msg, *args, **kwargs): # watch for 'level' in kwargs
logger.exception("Like, Oh My God!", level=logging.INFO)
Which do you all prefer?
I have to declare a preference for Mark's and Guido's approach. The reason is - what is a "normal" exception? And if it is "normal", why do we need to see a stack trace for it? In an exception handler, you can do "normal" logging - via debug(), info(), warn(), error() or fatal(). That would cater for "normal" exceptions (e.g. due to receiving bad data from external sources). You are using exception() solely to indicate that you want to send traceback info to the log. In practice, I haven't seen much value in putting traceback info in the log unless it's really needed - otherwise it just makes the log harder to read. If it's that important to have the level explicitly specified with a traceback, why not make it a mandatory argument to exception? If you don't want to clutter every exception call with the level argument, I think an additional "exception_ex" method (not that name, exactly! but you know what I mean) could be added for those people who really need it. ... def exception(self, level, msg, *args): ...
What's the current status of this PEP? I like the general outline of stuff, but it feels a little fuzzy. That is, it seems to present a high-level description and a bunch of options. I'd like to see the concrete APIs for all of the objects.
The current status is that I have been slowly working on it in private. I agree that the current PEP is fuzzy.
I'd also love to get at least a prototype implementation. It would be need to replace the zLOG in Zope3 with a new logging implementation that follows the PEP.
Last week (I think) Vinay Sajip posted on python-list that he had a
prototype
implementation of PEP 282. http://www.red-dove.com/python_logging.html I like what he has and I hope to work with him (merging with what implementation scraps I have) to build the eventual proposed patch for the PEP. I haven't told him this yet though. :) Vinay, what do you think?
No problem in principle. I'm about to post version 0.4, which has a whole lot of additional functionality. It now looks pretty close to PEP 282 (bar things like some method and class names), and I could use your help pinpointing areas where it doesn't match. What areas do your "implementation scraps" cover? Below is the change history for 0.4. You'll see that most of the previous "to do"s are history :-) Regards Vinay Sajip -------------------------------------------- Incorporated comments/patches from Ollie Rutherfurd: -Added level filtering for handlers. -Return root logger if no name specified in getLogger. Incorporated comments from Greg Ward: -Added distutils setup.py script. Added formatter initialization in Handler.__init__. Tidied up docstrings. Added removeHandler to Logger. Added removeFilter to Logger and Handler. logrecv.py modified to keep connection alive until client closes it. SocketHandler modified to not reset connection after each logging event. Added shutdown function which closes open sockets etc. Renamed DEFAULT_LOGGING_PORT->DEFAULT_TCP_LOGGING_PORT. Added DEFAULT_UDP_LOGGING_PORT. Added log_test4.py (example of arbitrary levels). Added addLevelName, changed behaviour of getLevelName. Fixed bugs in DatagramHandler. Added SMTPHandler implementation. Added log_test5.py to test SMTPHandler. Added SysLogHandler (contribution from Nicolas Untz based on Sam Rushing's syslog.py). Modified log_test1.py to add a SysLogHandler. Added rollover functionality to FileHandler. Added NTEventLogHandler (based on Win32 extensions). Added log_test6.py to test NTEventHandler. Added MemoryHandler implementation. Added log_test7.py to test MemoryHandler. Added log_test8.py to test FileHandler rollover. --------------------------------------------

On Wed, 20 Mar 2002 22:57:44 -0000 "Vinay Sajip" <vinay_sajip@red-dove.com> wrote:
I have to declare a preference for Mark's and Guido's approach. The reason is - what is a "normal" exception? And if it is "normal", why do we need to see a stack trace for it?
ZEO is a component that lets clients on remote machines access a ZODB storage using a custon Python RMI. (The details aren't exactly important, except that it's a concrete example.) IOW, ZEO + ZODB is a client-server object database. It's possible for all sorts of things to go wrong for an individual RMI. There can be a problem marshalling or unmarshalling the objects. There can be a problem with executing the actual methods on the server. There can be a problem with application code that is executed as a result of calling some method on the server. Etc. Few of these errors are fatal. Some of them may be uninteresting most of the time. So I'd like to log tracebacks and vary the log level depending on the importance of the event that caused the exception. One particular example is a KeyError. The server will raise a KeyError if the client asks for an object that doesn't exist. There are clients that catch this error on the client side and handle it cleanly. It's not an important exception for them. But I'd like to log the traceback on the server to handle cases where the client isn't expecting it. I'd probably log this at debug() level. Most of the time, nobody cares about a KeyError. But it might occasionally be useful when debugging something. Jeremy

ZEO is a component that lets clients on remote machines access a ZODB storage using a custon Python RMI. (The details aren't exactly important, except that it's a concrete example.) Point taken. In my current implementation, there is a generalized logging method in class Logger:
def log(self, level, msg, *args): I propose to add an additional method: def logException(self, level, exc_info, msg, *args) This should do the trick, I hope. The 0.4 version will include this method. Regards Vinay

On Wed, 20 Mar 2002 23:29:04 -0000
I propose to add an additional method:
def logException(self, level, exc_info, msg, *args)
This should do the trick, I hope. The 0.4 version will include this method.
I really like the shorthands for log levels, e.g. logger.debug() instead of logger.log(DEBUG, ...). So I'd be disappointed if I couldn't use it when I want to log an exception. Jeremy

I really like the shorthands for log levels, e.g. logger.debug() instead of logger.log(DEBUG, ...). So I'd be disappointed if I couldn't use it when I want to log an exception.
Would you be happy with a parallel set of xxxException() methods for each of the convenience methods debug(), info() etc? These are easy to provide, library module size is the only real issue. Regards Vinay

[Vinay Sajip wrote]
ZEO is a component that lets clients on remote machines access a ZODB storage using a custon Python RMI. (The details aren't exactly important, except that it's a concrete example.) Point taken. In my current implementation, there is a generalized logging method in class Logger:
def log(self, level, msg, *args):
I propose to add an additional method:
def logException(self, level, exc_info, msg, *args)
No need to pass in the exc_info if you are already specifying that an exception is being logged via log*Exception*. How about this (just an idea): def info(self, msgOrException, *args): if isinstance(msgOrException, Exception): # format the exception and msg, args = args[0], args[1:] else: # treat it like before try: # ... except Exception, ex: log.info(ex, "Eeeeek!") Trent -- Trent Mick TrentM@ActiveState.com

[Trent]
How about this (just an idea): def info(self, msgOrException, *args): if isinstance(msgOrException, Exception): # format the exception and msg, args = args[0], args[1:] else: # treat it like before
try: # ... except Exception, ex: log.info(ex, "Eeeeek!")
I don't like this. I think it rare you will want to log an exception with no contextual message. Eg, info(sys.exc_info()) Would be pretty useless in a log. You would also want to include *what* went wrong (whereas the exception generally only tells you *why* (and where) it went wrong. Mark.

[Mark Hammond wrote]
[Trent]
How about this (just an idea): def info(self, msgOrException, *args): if isinstance(msgOrException, Exception): # format the exception and msg, args = args[0], args[1:] else: # treat it like before
try: # ... except Exception, ex: log.info(ex, "Eeeeek!")
I don't like this. I think it rare you will want to log an exception with no contextual message.
But you still *can* log contextual information. What I meant and should have shown is: def info(self, firstarg, *args): if isinstance(firstarg, Exception): exc = firstarg msg, args = args[0], args[1:] else: exc = None msg, args = firstarg, args # ... (if exc is not None then format the exception and append to # msg%args try: # ... except Exception, ex: log.info(ex, "Eeeeek! %s %s %s", var1, var2, var3) basically this log() implementation is just trying to do: def log(self, exception, msg, *args): #... def log(self, msg, *args): #... ...in a language that does not support method overloading. Kinda klugdy? Maybe, though I would rather have: def exception(self, msg, *args, *kwargs): if not kwargs.has_key("level"): level = logging.ERROR else: level = kwargs["level"] #... than: def debugException(...) def infoException(...) def warnException(...) def errorException(...) def fatalException(...) def logException(...) [Guido on logging an exception at a level other than ERROR]
If this use case is rare enough, maybe a better approach would be to let you format it yourself using the traceback module?
then just have: def exception(self, msg, *args): #... Startin' to sound good to me. Trent -- Trent Mick TrentM@ActiveState.com

But you still *can* log contextual information. What I meant and should have shown is:
def info(self, firstarg, *args): if isinstance(firstarg, Exception): exc = firstarg msg, args = args[0], args[1:] else: exc = None msg, args = firstarg, args # ... (if exc is not None then format the exception and append to # msg%args
try: # ... except Exception, ex: log.info(ex, "Eeeeek! %s %s %s", var1, var2, var3)
basically this log() implementation is just trying to do:
def log(self, exception, msg, *args): #... def log(self, msg, *args): #...
...in a language that does not support method overloading.
Kinda klugdy? Maybe, though I would rather have:
def exception(self, msg, *args, *kwargs): if not kwargs.has_key("level"): level = logging.ERROR else: level = kwargs["level"] #...
than:
def debugException(...) def infoException(...) def warnException(...) def errorException(...) def fatalException(...) def logException(...)
I agree that xxxException() methods are not really needed. Consider this: I've provided a single logException() method (in 0.4, announcement just posted to c.l.py), and it is certainly *functionally* enough to get the job done. My suggestion about xxxException() methods was in response to Jeremy saying he'd really like the convenience methods. But it's probably too high a price to pay for calls which only occur in exception handlers - which will be a small proportion of the code in any (non-pathological!) system. In most cases, people will be looking to associate traceback information with errors. For them, logger.exception(msg, args) is just the ticket. For the less common cases, a call like logger.logException(logging.WARN, sys.exc_info(), msg, args) does everything that is needed. This, it seems to me, is the simplest interface which gives the desired flexibility. Even exception(...) is just shorthand for logException(ERROR, sys.exc_info(), ...) So at the cost of one extra method in the interface, we keep the implementation neat, clean and easy to understand and change. (And it's not even "extra", in the sense that it's already there ...) For my money, the above two methods provide the best value. Regards Vinay

def logException(self, level, exc_info, msg, *args)
No need to pass in the exc_info if you are already specifying that an exception is being logged via log*Exception*. Yes, except that you are assuming (I think) that logException would call sys.exc_info() to get the exception info. This is fine for most cases, but what about if the exception info needs to come from somewhere else (e.g. from a pickled message sent over the wire)? I haven't yet thought through all the issues about passing tracebacks etc. across sockets...my proposed interface just seemed a little more general.
How about this (just an idea): def info(self, msgOrException, *args): if isinstance(msgOrException, Exception): # format the exception and msg, args = args[0], args[1:] else: # treat it like before
try: # ... except Exception, ex: log.info(ex, "Eeeeek!") I have to admit I find these kinds of "special cases" worthy of caution. I'd much rather provide a parallel set of xxxException() methods - I think they would result in less "gotcha"s for the occasional (or inexperienced) user. Plus - at some point we are going to have to think about performance - and then we may regret overcomplicating the usual case code path for less common scenarios.
Regards Vinay

[Vinay Sajip wrote]
def logException(self, level, exc_info, msg, *args)
No need to pass in the exc_info if you are already specifying that an exception is being logged via log*Exception*.
Yes, except that you are assuming (I think) that logException would call sys.exc_info() to get the exception info. what about if the exception info needs to come from somewhere else (e.g. from a pickled message sent over the wire)? I haven't yet thought through all the issues about passing tracebacks etc. across sockets...
Touche, I haven't thought about that either and don't know if I am qualified to determine if that is a common enough case. If it is *not* that common then (forgetting the 'level'-issue for the moment) you have to admit that: try: ... except: log.exception("My britches are burning!") sure is a log nicer than try: ... except: log.logException(sys.exc_info(), "My britches are burning!") Trent -- Trent Mick TrentM@ActiveState.com

[Trent]
try: ... except: log.exception("My britches are burning!")
sure is a log nicer than
try: ... except: log.logException(sys.exc_info(), "My britches are burning!")
But you don't need to use the latter. For logging exceptions with a level of ERROR, the former construction works. For the case where you want to log an exception with (e.g.) a DEBUG level, then you would say log.logException(DEBUG, sys.exc_info(), "My britches are burning!") A little long-winded, perhaps, but not *that* bad. It has the benefit of making it clear that even though it's a DEBUG level call, exception info is to be sent to the log. Even in Jeremy's example of ZODB/ZEO, I would imagine that the number of instances where you need to make the second kind of logging call are relatively (i.e. proportionately) few - from his explanation I infer that he was talking about "catch-all" exception handlers which are usually at the higher levels of an application. Anyway, to change the subject a bit, what are your thoughts on configuration? As this is the area where the PEP and the present implementation are both a little weak, I'd like to know more about your ideas... Regards Vinay

On Wed, 20 Mar 2002 18:13:07 -0800 Trent Mick <trentm@ActiveState.com> wrote:
Touche, I haven't thought about that either and don't know if I am qualified to determine if that is a common enough case. If it is *not* that common then (forgetting the 'level'-issue for the moment) you have to admit that:
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
try: ... except: log.exception("My britches are burning!")
sure is a log nicer than
try: ... except: log.logException(sys.exc_info(), "My britches are burning!")
If I would implement debug() and other helper methods, I'd still like to see it as a keyword argument. I assume the various methods would be connected something like this: class Logger: def log(self, msg, level, exc=None): "Log msg at level w/ optional traceback for exc." def debug(self, msg, exc=None): self.log(msg, DEBUG_LEVEL, exc) def exception(self, msg, exc=None): if exc is None: exc = sys.exc_info() self.log(msg, ERROR_LEVEL, exc) This doesn't seem complicated or particularly slow. Jeremy

[Jeremy Hylton wrote]
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
Yes, I suppose so.
If I would implement debug() and other helper methods, I'd still like to see it as a keyword argument. I assume the various methods would be connected something like this:
class Logger: def log(self, msg, level, exc=None): "Log msg at level w/ optional traceback for exc."
def debug(self, msg, exc=None): self.log(msg, DEBUG_LEVEL, exc)
def exception(self, msg, exc=None): if exc is None: exc = sys.exc_info() self.log(msg, ERROR_LEVEL, exc)
This doesn't seem complicated or particularly slow.
Yup, I agree. Except it'll look like this: class Logger: def log(self, level, msg, *args, **kwargs): "Log msg at level w/ optional traceback for 'exc' keyword arg." if level < self.getEffectiveLevel(): return # construct LogRecord # pass LogRecord onto appropriate Handlers def debug(self, msg, *args, **kwargs): self.log(DEBUG, msg, *args, **kwargs) def exception(self, msg, *args, **kwargs): if not kwargs.has_key("exc") kwargs["exc"] = sys.exc_info() self.log(ERROR, msg, *args, **kwargs) Jeremy, The methods use 'msg, *args' instead of just 'msg' to avoid string interpolation if the message will not be logged. I think that that is still important to keep, albeit it making the 'exc' keyword argument less explicit. Vinay, Here is why I really like this idea now. As per usual, any log message call has the 'msg, *args' arguments. But, in addition, arbitrary objects can be passed in as keyword arguments. These objects get slapped into the LogRecord's __dict__ and any subsequent Handler and/or Formatter can work with those objects. For example, if I want to log some arbitrarily complex object I can just add it to the log record. On the handling end, I could have a Formatter that knows how to deal with that object. For formatters that *don't* recognize certain objects, a reasonable default like using the pprint module could be used. Granted this isn't as generic as log4j's ObjectRenderer system but it allows the system to grow into that cleanly. Pros: - Jeremy is happy, he can easily log exceptions to specific levels using the nice info(), debug(), etc. routines. - logger.exception("Eeek: %s", arg) is there for easy use - logger.exception("Eeek: %s", arg, exc=sys.exc_info()) is there when one need be explicit about where exc_info comes from. Cons: - The signature ...**kwargs... doesn't make it clear that a special key for exception info will be handled. - This could be construed as too much generality. It may complicate the LogRecord and Formatter in that they have to deal with arbitrary keyword args. (Mind you, I don't think that that needs to be any issue. For a first pass any keyword arg other than 'exc', or whatever it is called, could be ignored.) Trent -- Trent Mick TrentM@ActiveState.com

Here is why I really like this idea now. As per usual, any log message call has the 'msg, *args' arguments. But, in addition, arbitrary objects can be passed in as keyword arguments. These objects get slapped into the LogRecord's __dict__ and any subsequent Handler and/or Formatter can work with those objects. For example, if I want to log some arbitrarily complex object I can just add it to the log record. On the handling end, I could have a Formatter that knows how to deal with that object. For formatters that *don't* recognize certain objects, a reasonable default like using the
[Trent] pprint
module could be used.
I like this. I prefer the name "exc_info" (rather than "exc") for the key, as it is clearer that the result of sys.exc_info(), or equivalent, is being passed. How about the following? The kwargs is searched for "exc_info", and if found it is copied to the LogRecord [and removed from kwargs]. The kwargs dict is then bound to a "user_info" attribute of the LogRecord.
Cons: - The signature ...**kwargs... doesn't make it clear that a special key for exception info will be handled. Agreed, though spelling this out in the docstring will mitigate this... - This could be construed as too much generality. It may complicate the LogRecord and Formatter in that they have to deal with arbitrary keyword args. (Mind you, I don't think that that needs to be any issue. For a first pass any keyword arg other than 'exc', or whatever it is called, could be ignored.) There is a potential minefield here - if we are allowing any logging record to be sent by wire to a remote logger, then the "user_info" needs to go too - and what if something in it can't be pickled? Given that it is really important that the logging system is silent except when explicitly asked to do something by a logging call, exceptions caught in the logging system are generally ignored by design. This means that pickling exceptions would not be raised, and I foresee difficulties for developers... The idea of allowing arbitrary objects into the LogRecord is very powerful and has much to commend it, but I think the pickling problem may need to be solved first.
What do you think? Regards, Vinay

[Vinay Sajip wrote]
[Trent]
Here is why I really like this idea now. As per usual, any log message call has the 'msg, *args' arguments. But, in addition, arbitrary objects can be passed in as keyword arguments. These objects get slapped into the LogRecord's __dict__ and any subsequent Handler and/or Formatter can work with those objects. For example, if I want to log some arbitrarily complex object I can just add it to the log record. On the handling end, I could have a Formatter that knows how to deal with that object. For formatters that *don't* recognize certain objects, a reasonable default like using the pprint module could be used.
I like this. I prefer the name "exc_info" (rather than "exc") for the key, as it is clearer that the result of sys.exc_info(), or equivalent, is being passed.
Agreed. I prefer 'exc_info' too.
How about the following? The kwargs is searched for "exc_info", and if found it is copied to the LogRecord [and removed from kwargs]. The kwargs dict is then bound to a "user_info" attribute of the LogRecord.
There is a potential minefield here - if we are allowing any logging record to be sent by wire to a remote logger, then the "user_info" needs to go too - and what if something in it can't be pickled? Given that it is really important that the logging system is silent except when explicitly asked to do something by a logging call, exceptions caught in the logging system are generally ignored by design. This means that pickling exceptions would not be raised, and I foresee difficulties for developers... The idea of allowing arbitrary objects into the LogRecord is very powerful and has much to commend it, but I think the pickling problem may need to be solved first.
What do you think?
How about pulling out exc_info above, as you described, and then just dumping the rest of the keyword args. No user_info, no mine field. If we come up with a "mine sweeper" later then we can decide to pass on other keywords if it looks reasonable and useful. So effectivly we are just trying to express: def log(self, level, msg, *args, exc_info=None): ... but have to use: def log(self, level, msg, *args, **kwargs): ... # dump anything but kwargs["exc_info"] because the former is a syntax problem. Thoughts? Trent p.s. Vinaj, when do you sleep? :) -- Trent Mick TrentM@ActiveState.com

[Trent Mick wrote]
How about pulling out exc_info above, as you described, and then just dumping the rest of the keyword args. No user_info, no mine field. If we come up with
Note that by "dumping" I mean to just drop them. I do *not* mean to "print them to stdout", JavaScript's dump() notwithstanding. Trent -- Trent Mick TrentM@ActiveState.com

Vinay Sajip wrote:
There is a potential minefield here - if we are allowing any logging record to be sent by wire to a remote logger, then the "user_info" needs to go too - and what if something in it can't be pickled? Given that it is really important that the logging system is silent except when explicitly asked to do something by a logging call, exceptions caught in the logging system are generally ignored by design. This means that pickling exceptions would not be raised, and I foresee difficulties for developers... The idea of allowing arbitrary objects into the LogRecord is very powerful and has much to commend it, but I think the pickling problem may need to be solved first.
You could have the logging methods return an int/bool, 1 if succesfully logged, 0 on failure. Let the caller decide what to do. You could even return a failure object or None. The failure object would contain (or be) the exception/problem. Neal

You could have the logging methods return an int/bool, 1 if succesfully logged, 0 on failure. Let the caller decide what to do. You could even return a failure object or None. The failure object would contain (or be) the exception/problem. This violates the principle that logging should be "set and forget". Having
[Neal Norwitz] the logger fail silently has, in past experience, caused fewer problems than having to handle errors from the logging system - yet another headache for the developer! Regards Vinay

This violates the principle that logging should be "set and forget". Having the logger fail silently has, in past experience, caused fewer problems than having to handle errors from the logging system - yet another headache for the developer!
+1. (You could have a meta-logger where errors from the logger go -- e.g. default to stderr.) --Guido van Rossum (home page: http://www.python.org/~guido/)

Vinay Sajip wrote:
You could have the logging methods return an int/bool, 1 if succesfully logged, 0 on failure. Let the caller decide what to do. You could even return a failure object or None. The failure object would contain (or be) the exception/problem. This violates the principle that logging should be "set and forget". Having
[Neal Norwitz] the logger fail silently has, in past experience, caused fewer problems than having to handle errors from the logging system - yet another headache for the developer!
This is my point. It will almost always be ignored...unless the caller really needs to know the information. The logger will fail silently, only an object will be returned (no exception thrown) and the caller can safely ignore it. Neal

[Neal Norwitz wrote]
Vinay Sajip wrote:
You could have the logging methods return an int/bool, 1 if succesfully logged, 0 on failure. Let the caller decide what to do. You could even return a failure object or None. The failure object would contain (or be) the exception/problem. This violates the principle that logging should be "set and forget". Having
[Neal Norwitz] the logger fail silently has, in past experience, caused fewer problems than having to handle errors from the logging system - yet another headache for the developer!
This is my point. It will almost always be ignored...unless the caller really needs to know the information. The logger will fail silently, only an object will be returned (no exception thrown) and the caller can safely ignore it.
You cannot necessarily know if there has been a problem with a logging call by the time the logging call returns. That logging call could be asynchronous. Trent -- Trent Mick TrentM@ActiveState.com

"VS" == Vinay Sajip <vinay_sajip@red-dove.com> writes:
VS> I like this. I prefer the name "exc_info" (rather than "exc") VS> for the key, as it is clearer that the result of sys.exc_info(), VS> or equivalent, is being passed. How about the following? The VS> kwargs is searched for "exc_info", and if found it is copied to VS> the LogRecord [and removed from kwargs]. The kwargs dict is then VS> bound to a "user_info" attribute of the LogRecord. Why do you need **kwargs at all? Can't the interface specify the complete set of optional arguments explicitly? Jeremy

[Jeremy Hylton wrote]
"VS" == Vinay Sajip <vinay_sajip@red-dove.com> writes:
VS> I like this. I prefer the name "exc_info" (rather than "exc") VS> for the key, as it is clearer that the result of sys.exc_info(), VS> or equivalent, is being passed. How about the following? The VS> kwargs is searched for "exc_info", and if found it is copied to VS> the LogRecord [and removed from kwargs]. The kwargs dict is then VS> bound to a "user_info" attribute of the LogRecord.
Why do you need **kwargs at all? Can't the interface specify the complete set of optional arguments explicitly?
If, as I said, we want to keep the '*args' argument, how do we explicitly specify an 'exc_info' argument? def info(self, msg, *args, exc_info=None): ^------- SyntaxError Trent -- Trent Mick TrentM@ActiveState.com

"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> [Jeremy Hylton wrote]
Why do you need **kwargs at all? Can't the interface specify the complete set of optional arguments explicitly?
TM> If, as I said, we want to keep the '*args' argument, how do we TM> explicitly specify an 'exc_info' argument? TM> def info(self, msg, *args, exc_info=None): TM> ^------- SyntaxError
def info(self, msg, exc_info=None, *args): ... return msg, exc_info, args ... info(1, 2, 3, 4, 5) (2, 3, (4, 5))
Jeremy

[Jeremy Hylton wrote]
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
[Trent]
Yes, I suppose so.
I'd like to call YAGNI on this. Also on the idea of being able to pass an exception to all logging levels. Also on the idea of passing the exc_info in rather than having the logger call sys.exc_info(). Instead, the logger should be subclassable or extensible so that Jeremy can implement this functionality if he really wants it. KISS. --Guido van Rossum (home page: http://www.python.org/~guido/)

[Guido]
I'd like to call YAGNI on this. Also on the idea of being able to pass an exception to all logging levels. Also on the idea of passing the exc_info in rather than having the logger call sys.exc_info().
Instead, the logger should be subclassable or extensible so that Jeremy can implement this functionality if he really wants it.
KISS. Amen to that. As to Logger extensibility - I will add a setLoggerClass(klass) which tells the getLogger() [a factory method] which class to use when instantiating a logger.
Regards, Vinay Sajip

[Guido van Rossum wrote]
[Jeremy Hylton wrote]
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
[Trent]
Yes, I suppose so.
I'd like to call YAGNI on this. Also on the idea of being able to pass an exception to all logging levels. Also on the idea of passing the exc_info in rather than having the logger call sys.exc_info().
Instead, the logger should be subclassable or extensible so that Jeremy can implement this functionality if he really wants it.
KISS.
Alrigtie. Trent -- Trent Mick TrentM@ActiveState.com

"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> [Guido van Rossum wrote]
[Jeremy Hylton wrote]
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
[Trent]
Yes, I suppose so.
I'd like to call YAGNI on this. Also on the idea of being able to pass an exception to all logging levels. Also on the idea of passing the exc_info in rather than having the logger call sys.exc_info().
Instead, the logger should be subclassable or extensible so that Jeremy can implement this functionality if he really wants it.
What's the point here? I've presented use cases, and I maintain a large body of ZODB/ZEO code that already uses these features. So it simply doesn't make sense to say "You Ain't Gonna Need It" because I already need it and zLOG already has it. The feature is simple to explain and implement, and seems to have low implementation cost. So I certainly think it meets the simplest thing that could possibly work criteria. Jeremy

[Jeremy, in response to Guido's YAGNI]
What's the point here? I've presented use cases, and I maintain a large body of ZODB/ZEO code that already uses these features. So it simply doesn't make sense to say "You Ain't Gonna Need It" because I already need it and zLOG already has it.
Okay, we can have our cake and eat it too. (You've gotta love Python.) Suppose the current module interface stays as it is: debug(), info(), warn(), error() and fatal() all take (self, msg, *args) as arguments. exception() has the same arg list but, since it knows it's called in an exception handling context, it passes sys.exc_info() along. Simple, clean interface. Here's log_test10.py, a working example script which shows how easy it is to roll your own logger. # -- log_test10.py --------------------------- import sys import locale locale.setlocale(locale.LC_ALL, '') from logging import * LOG_FORMAT = "%(asctime)s %(level)-5s %(message)s" DATE_FORMAT = "%x %X" class MyLogger(Logger): """ A simple example of a logger extension. """ def debug(self, msg, *args): """ This overridden method passes exception information for DEBUG level calls """ if self.manager.disable >= DEBUG: return if DEBUG >= self.getChainedPriority(): ei = sys.exc_info() if not ei[1]: ei = None self._log(DEBUG, msg, args, ei) del ei def config(): setLoggerClass(MyLogger) basicConfig() getRootLogger().handlers[0].setFormatter(Formatter(LOG_FORMAT, DATE_FORMAT)) def run(): logger = getLogger("mylogger") logger.info("Starting...") logger.debug("Debug message not in exception handler (no traceback)") logger.info("About to throw exception...") try: print "7" + 4 except Exception, e: logger.debug("Debug message inside exception handler (traceback)") logger.info("Done.") shutdown() if __name__ == "__main__": config() run() #-- end of log_test10.py --------------------------- When run, you get... -- output ------------------------- 21/03/02 23:33:04 INFO Starting... 21/03/02 23:33:05 DEBUG Debug message not in exception handler (no traceback) 21/03/02 23:33:05 INFO About to throw exception... 21/03/02 23:33:05 DEBUG Debug message inside exception handler (traceback) Traceback (innermost last): File "log_test10.py", line 45, in run print "7" + 4 TypeError: illegal argument type for built-in operation 21/03/02 23:33:05 INFO Done. -- output ------------------------- I rest my case (I hope...) Regards Vinay

So the decision is between the simpler: class Logger: def info(self, msg, *args): ... def exception(self, msg, *args): """Log sys.exc_info() at the ERROR level""" and the slightly more complex: class Logger: def info(self, msg, *args, **kwargs): """Use kwargs["exc_info"] to log an exception at this level.""" def exception(self, msg, *args, **kwargs): """Optionally pass in kwargs["exc_info"] otherwise sys.exc_info() is called to log exception information at the ERROR level.""" The argument for the latter is that is adds the capability of: (1) Logging an exception at a level other than ERROR (for which Jeremy maintains there is a string use case and for which Kevin Butler gives a simpler use case); and (2) Optionally explicitly passing in exc_info, that may differ from sys.exc_info(). The argument for the former is: (1) KISS and YAGNI (2) You should just be able to subclass Logger and add the functionality you want. This is only a string point if the given use cases are not at all common. (3) If you want to pass in exc_info other than sys.exc_info() then format it yourself or subclass Logger. Thoughts? Trent -- Trent Mick TrentM@ActiveState.com

"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> The argument for the former is: TM> (1) KISS and YAGNI I've said my piece for the most part, but this YAGNI stuff bugs me. YAGNI means: Always implement things when you actually need them, never when you just foresee that you need them. [From http://www.xprogramming.com/Practices/PracNotNeed.html] I actually need this feature. If the standard logger doesn't have it, we'll have to extend Zope with a Zope-specific logger that adds the feature. I'm not asking for a feature because I think it might be useful. Rather, I'd think that Kevin and I are offering some real-world experience. We've written or maintain large, production-quality applications that use logging. In Zope, it has been useful to log tracebacks for exceptions at levels other than error. And it has been necessary to pass an exception explicitly because sys.exc_info() would get stomped by a subsequent exception. Now, KISS is different. It may be that the kinds of things that Kevin and I are trying to do are so unusual that no one else will ever do it. By this argument, supporting a few cases from oddball customers makes your code harder to understand and maintain; perhaps other users will find the interface confusing because of the extra features. Jeremy

[Jeremy]
... Now, KISS is different. It may be that the kinds of things that Kevin and I are trying to do are so unusual that no one else will ever do it. By this argument, supporting a few cases from oddball customers makes your code harder to understand and maintain; perhaps other users will find the interface confusing because of the extra features.
Just because Guido and Trent think Zope is extreme doesn't mean you should run off arguing to get rid of floats, complexes, Unicode, weakrefs, rotor, imp, curses, posixfile, gopherlib, xdrlib, mailcap and Macintosh support. can't-decide-whether-i'm-winking-ly y'rs - tim

[Jeremy, on logging tracebacks at any level]
I actually need this feature. If the standard logger doesn't have it, we'll have to extend Zope with a Zope-specific logger that adds the feature. I'm not asking for a feature because I think it might be useful. Let me try and summarise - perhaps only for my own benefit :-)
1. Jeremy would like to be able to send traceback information from a logging call at any level. 2. Guido is not keen on passing in exc_info from client code - the logger should call sys.exc_info() automatically when required. And the "when" is the crux of it... 3. In the context of an exception handler, a developer may want to send different things to different loggers - e.g. #Assume the traceback will provide info on which table, record, etc. caused the problem adminEmailer.exception("A record lock prevented completion. Please deal with the mess.") #We'd like to tell the developers the exact context too, as a diagnostic aid developers.info("Yet another record locking problem was seen. Why oh why?") #No traceback needed for this, it's just for the record (no pun intended) dbstats.info("%-20s %s '%s'", "RECORD LOCK", self.table, self.key) In the first call, the logic of "when" is built into the choice of call. In the other two calls, we'd like to use the same method, but have it behave differently in the two cases. Now, the decision about tracebacks has to be made at *some* point. Let's assume that wherever that decision is made, we can just call the low-level logging method _log(), as defined below: class Logger: ... def _log(self, level, want_traceback, msg, args_tuple): """Low level logging method""" #Do the needful - if want_traceback is true, call sys.exc_info() and log traceback info ... So, the convenience methods become something like: def log(self, level, msg, *args): """For cases where the level needs to be computed by the caller at run time""" if self.enabledFor(level): self._log(level, 0, msg, args) def info(self, msg, *args): if self.enabledFor(INFO): self._log(INFO, 0, msg, args) #and analogously for DEBUG, WARN, ERROR, FATAL and then def exception(self, msg, *args): """Use when handling an exception""" if self.enabledFor(ERROR): self.log(ERROR, 1, msg, args) Now, I hope all would agree that for "normal" use, the interface is fairly minimal and in keeping with XP principles. In fact, some would argue that debug(), info() etc. are redundant - so you can think of them as syntactic sugar. Now to deal with the case of tracebacks at arbitrary levels, I implemented def logException(self, level, msg, *args): """The same as log, except that it sends a 1 to _log for want_traceback""" if self.enabledFor(level): self._log(level, 1, msg, args) But I think we all neglected to see that by implementing exception with one extra argument, the whole controversy goes away. Just remove logException and change exception so that it becomes def exception(self, level, msg, *args): """Use when handling an exception""" if self.enabledFor(level): self.log(level, 1, msg, args) We've effectively renamed logException() as exception(). There's not a lot to choose between logger.exception(INFO, msg, args) and logger.exception(msg, args) If people can't stomach having to put the level into every call to exception, then we can keep exception as it was [syntactic sugar] and reintroduce logException (perhaps change the name, it's a bit of a sore thumb). If any more syntactic sugar is wanted, then I think it's reasonable for people to roll their own classes. For example, some users want to use their own levels, no doubt for their own good reasons. The current implementation supports this. They can use log() and logException(), but debug(), info(), warn(), error(), fatal() and exception() are useless to them - and so exposed as the syntactic sugar they really are. So if users really want, they can define a level BAD_HAIR_DAY and a corresponding sugar method bad_hair_day(self, msg, *args) which calls either log() or logException(), as they please. It's not a job for the standard module, I'm sure all would agree. [Slightly OT] The above issues have generated an awful lot of discussion, and perhaps rightly so, but I'm conscious of time slipping by. I'd certainly like logging to make it into 2.3a1, and there are a lot of other, more important functional issues (such as: configurability) which need your thoughts and ideas. Can I make a plea for input in this area? I know that Python-dev people are busy people, so I don't know how many of you have had time to actually try out the implementation, but I think it would help the cause of PEP 282 if they gave specific feedback in other areas? For example: Packaging: I originally implemented it as a package with separate modules for logger, handler etc. and then consolidated into one module. I think one module is better, except for the specific handlers - they don't all need to be in the standard module. Perhaps MemoryHandler, StreamHandler and FileHandler. But others such as SysLogHandler, NTEventLogHandler, SMTPHandler, and perhaps even SocketHandler and DatagramHandler - should they be in the same module, or should they perhaps go into a separate "loghandlers" module which is also part of Python? Specific handler implementations: what improvements could be made? [Even more OT - for Monty Python fans] You know, it's really surprising that Python hasn't had a logging module before, especially given the "Lumberjack" song. I even thought of calling the package "Lumberjack" before the idea of putting it into core Python came up :-) Thanks & Regards Vinay Sajip

"VS" == Vinay Sajip <vinay_sajip@red-dove.com> writes:
VS> But I think we all neglected to see that by implementing VS> exception with one extra argument, the whole controversy goes VS> away. Just remove logException and change exception so that it VS> becomes VS> def exception(self, level, msg, *args): VS> """Use when handling an exception""" if VS> self.enabledFor(level): VS> self.log(level, 1, msg, args) VS> We've effectively renamed logException() as exception(). There's VS> not a lot to choose between VS> logger.exception(INFO, msg, args) VS> and VS> logger.exception(msg, args) VS> If people can't stomach having to put the level into every call VS> to exception, then we can keep exception as it was [syntactic VS> sugar] and reintroduce logException (perhaps change the name, VS> it's a bit of a sore thumb). The thing I like best about PEP 282 is the method names that indicate the log levels. While it's just a convenience, it's a handy one. It saves me having to explicitly manage my namespace to make those names visible. The Zope code base is littered with "from zLOG import LOG, INFO, DEBUG" lines. If I find a place where I need to add a TRACE message, I have to remember to check the import line to see if that name is bound. I've forgotton on more than one occasion, and since it's on an unusual code path, I don't find out until it fails at runtime. Now we can just use "import zLOG" and name everything explicitly, but that makes for a lot of extra typing: zLOG.LOG("Channel", zLOG.TRACE, "a trace message", exc=sys.exc_infO()) Now this is only a minor annoyance, but I'd be happier with the shorter and equally clear version log.trace("a trace message", exc=sys.exc_info()) VS> If any more syntactic sugar is wanted, then I think it's VS> reasonable for people to roll their own classes. I guess the question is how many users want this. If many users like this feature, we end up forcing everyone to roll their own classes. I don't have a good intuition about what the typical user of a logging module will want. Can log4j teach us anything? VS> some users want to use their own levels, no doubt for their own VS> good reasons. The current implementation supports this. They can VS> use log() and logException(), but debug(), info(), warn(), VS> error(), fatal() and exception() are useless to them - and so VS> exposed as the syntactic sugar they really are. So if users VS> really want, they can define a level BAD_HAIR_DAY and a VS> corresponding sugar method bad_hair_day(self, msg, *args) which VS> calls either log() or logException(), as they please. It's not a VS> job for the standard module, I'm sure all would agree. I agree. I also expect that most users will just use the standard levels. Jeremy

TM> The argument for the former is: TM> (1) KISS and YAGNI
[Jeremy]
I've said my piece for the most part, but this YAGNI stuff bugs me.
Then let me explain it some more. This logger module is not just for Zope, it's for all Python users. It's clear that for 99% of the uses, this *is* a case of YAGNI. Lots of standard Python modules lack advanced features so they can be more easy to understand or maintain. That's also the 80% principle, and I'm sticking to it. In many of those cases, users who need an advanced feature can easily add it by creating a subclass, or using other extensibility features (e.g. hooks). Occasionally, it's needed to rewrite the base functionality from scratch in order to add something new. If that happens too often, the library design should probably be revised. Zope is a sophisticated application that has needs that go far beyond those of most Python users. I'm very happy that it's proved possible to write Zope in Python. But I don't think it should be the measuring stick by which the functionality of the standard library must be measured. Here's another way to look at it. To support the base functionality, we need an X amount of code. To add the feature of logging exceptions at any level, we would need an additional Y amount. The number Y is much smaller than X, but not insignificant. If the size of a subclass that adds that feature is approximately equal to Y, I think it should be kept out of the base class in this case, because you can easily write that subclass for Zope. If writing such a subclass would require significantly more code than Y (the amount of code needed to integrate it directly into the base class), I agree that the base class needs to be reworked to make this easier to add, or even to support it outright. But I find that hard to believe in this case. [KISS] As a compromise, would it make sense to have 5 simple logging methods that indicate the level by their name and have no support for handling exceptions, and one more general function that takes an error level argument and an optional exc_info? That more general function would be useful in some other cases (e.g. for programs that want to tweak the logging levels for certain loggable events) and adding it is much less disturbing to the simplicity of the standard interface than adding exc_info support to each of the simple methods. It would mean that ZEO would have to use a slightly more expensive (because it's more general) interface in the one or two cases where it wants to log an exception at the info level. I doubt that these exceptions happen often enough to notice the difference. --Guido van Rossum (home page: http://www.python.org/~guido/)

"GvR" == Guido van Rossum <guido@python.org> writes:
TM> The argument for the former is: TM> (1) KISS and YAGNI GvR> [Jeremy]
I've said my piece for the most part, but this YAGNI stuff bugs me.
GvR> Then let me explain it some more. This logger module is not GvR> just for Zope, it's for all Python users. It's clear that for GvR> 99% of the uses, this *is* a case of YAGNI. This really isn't about YAGNI then. It's about some customers' needs being too complex to handle in the standard library. Jeremy

This really isn't about YAGNI then. It's about some customers' needs being too complex to handle in the standard library.
Why isn't that YAGNI? To me it feels like this is YAGNI for most users; we're designing a library module here, not a Zope component. I'd be happy to find a different principle with a catchy name (maybe the 80% principle or KISS are better) but I still think your use case is pretty unusual. --Guido van Rossum (home page: http://www.python.org/~guido/)

On 22 Mar 2002 at 12:03, Guido van Rossum wrote: [beating up Jeremy for being, em, unusual :-)]
Why isn't that YAGNI? To me it feels like this is YAGNI for most users; we're designing a library module here, not a Zope component.
I'd be happy to find a different principle with a catchy name (maybe the 80% principle or KISS are better) but I still think your use case is pretty unusual.
It doesn't take something as complex as Zope to want many of these features. I end up doing most of them (from scratch each time, of course - getting a round wheel out of linear code is a matter of successive approximation, which, fortunately, Python makes enjoyable) in any long-running server I write. Which is more than a few. I suspect that the people who are going to want it are under-represented in your sample of "users". System / application developers rarely get involved in language issues unless they're congenitally obnoxious <0.9 wink>. -- Gordon http://www.mcmillan-inc.com/

On Fri, Mar 22, 2002, Guido van Rossum wrote:
Why isn't that YAGNI? To me it feels like this is YAGNI for most users; we're designing a library module here, not a Zope component.
I'd be happy to find a different principle with a catchy name (maybe the 80% principle or KISS are better) but I still think your use case is pretty unusual.
+1 on "80%" Three reasons: * "Explicit is better than implicit" * YAGNI is a falsehood when directed at someone who does need the feature, even though you're using the plural "you" * I can never remember what YAGNI stands for; it's not that common, even here on python-dev -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "We should forget about small efficiencies, about 97% of the time. Premature optimization is the root of all evil." --Knuth

+1 on "80%"
Three reasons:
* "Explicit is better than implicit"
Yes.
* YAGNI is a falsehood when directed at someone who does need the feature, even though you're using the plural "you"
Yeah, the "you" is sort of generic.
* I can never remember what YAGNI stands for; it's not that common, even here on python-dev
But it will be. I can't stop using it. :-) --Guido van Rossum (home page: http://www.python.org/~guido/)

[Aahz]
... * I can never remember what YAGNI stands for; it's not that common, even here on python-dev
That's why it's needed here, although it came from a culture building payroll apps, not programming infrastructure. Heck, if Unix dweebs had applied YAGNI, ls wouldn't have 51 useless options <wink>.

Tim> Heck, if Unix dweebs had applied YAGNI, ls wouldn't have 51 useless Tim> options <wink>. I believe the first couple Unix programmers did, though I doubt they thought of it in precisely that acronym. It was the masses that followed (probably influenced by some expatriate VMS programmers) who got carried away with adding thirty-leven options to every little filter. <wink><wink> Skip

Skip Montanaro <skip@pobox.com>:
I believe the first couple Unix programmers did, though I doubt they thought of it in precisely that acronym. It was the masses that followed (probably influenced by some expatriate VMS programmers) who got carried away with adding thirty-leven options to every little filter. <wink><wink>
No, it wasn't the masses. It was the FSF. At this point, most people have forgotten what the Unix toolkit was like before the GNU project replaced most of it. Many, many fewer options per filter... -- <a href="http://www.tuxedo.org/~esr/">Eric S. Raymond</a>

Eric> Skip Montanaro <skip@pobox.com>: >> It was the masses that followed (probably influenced by some >> expatriate VMS programmers) who got carried away with adding >> thirty-leven options to every little filter. <wink><wink> Eric> No, it wasn't the masses. It was the FSF. I'm willing to go along with that idea as well. ;-) the-FSF-never-met-a-megabyte-it-didn't-like-ly, y'rs, Skip

Tim> Heck, if Unix dweebs had applied YAGNI, ls wouldn't have 51 useless Tim> options <wink>.
I believe the first couple Unix programmers did, though I doubt they thought of it in precisely that acronym. It was the masses that followed (probably influenced by some expatriate VMS programmers) who got carried away with adding thirty-leven options to every little filter. <wink><wink>
Skip
I remember that way back at CWI we called "the BSD disease" -- after our sysadmin had installed 4.0 BSD on our VAX, we were appalled by the code size growth of most common utilities. --Guido van Rossum (home page: http://www.python.org/~guido/)

"GvR" == Guido van Rossum <guido@python.org> writes:
GvR> Zope is a sophisticated application that has needs that go far GvR> beyond those of most Python users. I'm very happy that it's GvR> proved possible to write Zope in Python. But I don't think it GvR> should be the measuring stick by which the functionality of the GvR> standard library must be measured. This makes me want to ask a more fundamental question. What are the expected use cases for the standard logging module(s)? Who is the customer? I've never used a logging library before I used Zope, and never really thought I needed one. (Perhaps it would have been useful in Grail.) It seems that prints are good enough for small applications and that more complex logging libraries are mostly useful for sophisticated applications. If logging is mostly useful for sophisticated applications, it would be a mistake to declare their requirements as too complex. On the other hand, maybe Zope should continue to use its own logging tools. I may just be suffering from tunnel vision, but perhaps the PEP could have more text about the movitation. Why kinds of problems does it solve? For what kinds of programs? lets-write-a-logging-tool-for-ndiff-ly y'rs, Jeremy

"JH" == Jeremy Hylton <jeremy@zope.com> writes:
JH> I've never used a logging library before I used Zope, and JH> never really thought I needed one. (Perhaps it would have JH> been useful in Grail.) It seems that prints are good enough JH> for small applications and that more complex logging libraries JH> are mostly useful for sophisticated applications. If logging JH> is mostly useful for sophisticated applications, it would be a JH> mistake to declare their requirements as too complex. On the JH> other hand, maybe Zope should continue to use its own logging JH> tools. I think Jeremy has a good point here. IME, logging itself is unnecessary for for many Python programs, be they library modules or simple scripts. Once you get into doing things like writing web applications, or long running servers, where you typically don't have a stdout or stderr, the print approach fails, and you end up inventing a logging mechanism. I thought/hoped the whole point of this PEP was to give such application authors a common way to spell its logging facilities, so that end-users can have a common way to configure the logging of the applications they're installing and administering. All one line hacks eventually turn into scripts. All scripts eventually turn into multi-user applications. All multi-user applications eventually turn into web apps. All web apps eventually become distributed. ;) -Barry

[Barry A. Warsaw]
I think Jeremy has a good point here. IME, logging itself is unnecessary for for many Python programs, be they library modules or simple scripts.
It seems that having a logger module *at all* is a YAGNI for 99% of users 99% of the time. That suggests that, if it's a good idea to have one anyway, it should cater to the 1% of users who need it 1% of the time. This makes me uncomfortable about rejecting requirements from people who actually use logging heavily. Else we're going to reinvent parsermodule: all the basic pieces appear to be there, and only someone who actually needs its facilities will find out they've got to write a ton of stuff on top of it before getting something truly usable.

Hi, Tim Peters:
It seems that having a logger module *at all* is a YAGNI for 99% of users 99% of the time. That suggests that, if it's a good idea to have one anyway, it should cater to the 1% of users who need it 1% of the time. This
I disagree. How many programs write to stderr with some undefined stuff which is either too much or not enough information? Probably lots. The next user then wants to use that same program as a daemon and needs to figure out how to get the stuff to Syslog safely. The next one wants to use it as a module, link it into HTTP and needs to figure out how to package some of the text into an HTML error message. Thus, my take is that as soon as you have a sensible system to do logging with, far more than the 1% of people who "need logging" now will adapt to it.
makes me uncomfortable about rejecting requirements from people who actually use logging heavily.
I'd make the simple stuff easy and the complicated stuff possible. This approach suggests that the PEP in its current form is too complicated: - Formatters are associated with handlers -- it makes no sense to XML-format a Syslog message, and the default is going to be "msg % args" anyway. - Filters are handlers which don't print anything. Instead, they throw a StopLogging exception. Thoughts? -- Matthias Urlichs | noris network AG | http://smurf.noris.de/ -- The price of success in philosophy is triviality. -- C. Glymour.

Trent Mick wrote:
So the decision is between the simpler:
class Logger: def info(self, msg, *args): ...
def exception(self, msg, *args): """Log sys.exc_info() at the ERROR level"""
and the slightly more complex:
class Logger: def info(self, msg, *args, **kwargs): """Use kwargs["exc_info"] to log an exception at this level."""
def exception(self, msg, *args, **kwargs): """Optionally pass in kwargs["exc_info"] otherwise sys.exc_info() is called to log exception information at the ERROR level."""
The argument for the latter is that is adds the capability of: (1) Logging an exception at a level other than ERROR (for which Jeremy maintains there is a string use case and for which Kevin Butler gives a simpler use case); and (2) Optionally explicitly passing in exc_info, that may differ from sys.exc_info().
The argument for the former is: (1) KISS and YAGNI
It can't be YAGNI if there is a need.
(2) You should just be able to subclass Logger and add the functionality you want. This is only a string point if the given use cases are not at all common. (3) If you want to pass in exc_info other than sys.exc_info() then format it yourself or subclass Logger.
Thoughts?
We have a need to make it easy to include error traceback in logs regardless of the level. Here are a couple of other alternatives: C. Make arguments more explicit: class Logger: def info(self, msg, args=None, kwargs=None, exc_info=None): def exception(self, msg, args=None, kwargs=None, exc_info=None): I have to say that I don't like the trend toward use of *args and **kwargs to pass arguments that will be just grabbed and passed to something else. It provides only slight syntactic sure (especially for positional arguments) and adds complications, like making it hard to add arguments to the function. D. Provide an exception formatter that delays formatting until a message is logged. I don't like this so much because I'd prefer that the decision about whether to show a tracebeck be a policy of the back end. Jim -- Jim Fulton mailto:jim@zope.com Python Powered! CTO (888) 344-4332 http://www.python.org Zope Corporation http://www.zope.com http://www.zope.org

Jim> It can't be YAGNI if there is a need. Like many other people who have implemented web server type beasts, I added a logging capability to my XML-RPC server. I will wager that if you survey others who have written similar tools (from small servers to big things on the scale of Webware, Quixote, or Zope) that they have all implemented some sort of logging facility. It is definitely not something people aren't going to need (I *hate* that acronym - I'll keep spelling it out, thank you). Mine logger was written just for use with a single application, so doesn't have the bells and whistles others have apparently found indispensable. I would dump it in a flash if something was made available in the standard library even if it meant rewriting every line that makes calls to my current logger. My only functional request is that it be able to integrate with the Unix syslog facility so I can control where messages go with the same "tool" (a text editor + /etc/syslog.conf) that I've used for years. Skip

Skip Montanaro wrote:
...
Mine logger was written just for use with a single application, so doesn't have the bells and whistles others have apparently found indispensable. I would dump it in a flash if something was made available in the standard library even if it meant rewriting every line that makes calls to my current logger.
Yup. I look forward to changing all out zLOG calls to use the new standard logger. More important, I look forward to having all the other cool Python software I reuse use the same logging facility I use. :) Jim -- Jim Fulton mailto:jim@zope.com Python Powered! CTO (888) 344-4332 http://www.python.org Zope Corporation http://www.zope.com http://www.zope.org

"JF" == Jim Fulton <jim@zope.com> writes:
JF> Yup. I look forward to changing all out zLOG calls to use the JF> new standard logger. More important, I look forward to having JF> all the other cool Python software I reuse use the same JF> logging facility I use. :) Here, here! -Barry

I think having a default implementation of logging is a good idea. We have a number of logging systems for python that we have needed. PEP 282 would eliminate some of the logging code we have. Getting the logging system right is not easy. From the design discussions we have been through for a C++ logging system recently I would argue that the importance order is wrong. FRom PEP 282 DEBUG INFO WARN ERROR FATAL Suggested DEBUG WARN INFO ERROR FATAL The reasoning is that INFO messages affirms actions (Example: user logged on, call placed), whereas WARN messages can frequently be ignored. (Example: disk space is running low, old interface used) Of course if you implement them a categories instead of an ordered sequence the user can turn on and off the categories that are interesting. Barry

[Barry Scott]
Getting the logging system right is not easy. From the design discussions we have been through for a C++ logging system recently I would argue that the importance order is wrong.
FRom PEP 282 DEBUG INFO WARN ERROR FATAL
Suggested DEBUG WARN INFO ERROR FATAL
The reasoning is that INFO messages affirms actions (Example: user logged on, call placed), whereas WARN messages can frequently be ignored. (Example: disk space is running low, old interface used)
Of course if you implement them a categories instead of an ordered sequence the user can turn on and off the categories that are interesting. In the proposed implementation, you can implement whatever order you need for your application. But the suggested order in PEP282 makes more sense to me because INFO messages require no specific actions, whereas WARN messages do invite actions. Of course they can be ignored, though sometimes at peril of inviting disaster (e.g. if disk space actually gets exhausted). So the rationale is -
DEBUG - detail information, of use to developers trying to diagnose the cause of a problem. INFO - give you a warm feeling that things are going according to plan. WARN upwards - indication that some remedial action is required, with an indication of how bad things could get. Regards Vinay

Jim> It can't be YAGNI if there is a need. Maybe the term should be MPAGNI (Most People AGNI) or MOTTYAGNI (Most Of The Time YAGNI). Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

"GE" == Greg Ewing <greg@cosc.canterbury.ac.nz> writes:
Jim> It can't be YAGNI if there is a need. | Maybe the term should be MPAGNI (Most People AGNI) or | MOTTYAGNI (Most Of The Time YAGNI). Or YANIUYDATYHI (you ain't gonna need it until you do, and then you'll hate it). pronounced-yani-yoo-daddy-hi-ly y'rs, -B

On Tue, Mar 26, 2002, Greg Ewing wrote:
Jim> It can't be YAGNI if there is a need.
Maybe the term should be MPAGNI (Most People AGNI) or MOTTYAGNI (Most Of The Time YAGNI).
Which takes us right back to my +1 on "80%". -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "We should forget about small efficiencies, about 97% of the time. Premature optimization is the root of all evil." --Knuth

[Jim Fulton]
We have a need to make it easy to include error traceback in logs regardless of the level. I believe we have two possible mechanisms now: either (a) define a specific method which always includes tracebacks, and pass the level as a parameter: def exception(self, level, msg, *args): # log 'msg % args' with traceback at specified level
(b) use a keyword parameter on all of debug(), info(), warn(), error() and fatal(), all of which will have a common signature, for example def debug(self, msg, *args, **kwargs) # log 'msg % args' at DEBUG level, include traceback if kwargs ["exc_info"] evaluates to true. I'm equally comfortable with either approach. Though I share your reservations about kwargs, I'm persuaded by Kevin Butler's argument that the kwargs way allows greater extensibility. But either way will allow Zope logging the flexibility that Jeremy advocated.
D. Provide an exception formatter that delays formatting until a message is logged. I don't like this so much because I'd prefer that the decision about whether to show a tracebeck be a policy of the back end.
Formatting of the arguments is delayed for as long as possible after logging. A caller can check that the logger would actually record an event before bothering to compute the arguments for the message. if logger.isEnabledFor(logging.WARN): # compute arguments arg1, arg2 etc. (could be expensive) logger.warn(msg, arg1, arg2) Regards Vinay Sajip

Jeremy Hylton wrote:
"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> [Guido van Rossum wrote]
[Jeremy Hylton wrote]
It seems quite plausible to decide to log an exception and then get another trivial exception raised and caught on the way to the logger. You would still want to log the original exception, so passing it explicitly is helpful sometimes.
[Trent]
Yes, I suppose so.
I'd like to call YAGNI on this. Also on the idea of being able to pass an exception to all logging levels. Also on the idea of passing the exc_info in rather than having the logger call sys.exc_info().
Instead, the logger should be subclassable or extensible so that Jeremy can implement this functionality if he really wants it.
What's the point here? I've presented use cases, and I maintain a large body of ZODB/ZEO code that already uses these features. So it simply doesn't make sense to say "You Ain't Gonna Need It" because I already need it and zLOG already has it.
The feature is simple to explain and implement, and seems to have low implementation cost. So I certainly think it meets the simplest thing that could possibly work criteria.
Yes, this is an important feature. Thanks Jeremy. :) BTW, the PEP really should reference zLOG. It didn't the last time I looked. Jim -- Jim Fulton mailto:jim@zope.com Python Powered! CTO (888) 344-4332 http://www.python.org Zope Corporation http://www.zope.com http://www.zope.org

"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> Jeremy, The methods use 'msg, *args' instead of just 'msg' to TM> avoid string interpolation if the message will not be logged. TM> I think that that is still important to keep, albeit it making TM> the 'exc' keyword argument less explicit. I really like my logging interface, which also uses *args. This just seems really convenient, and like you said, allows you to avoid some extra work if the logging is disabled: syslog('vette', 'banned subscription: %s (matched: %s)', email, pattern) (There's a mismatch in my API and the PEP 282 API: `syslog' is a global Logger instance that takes the file to log to -- relative to a known directory -- as its first argument. I don't think this is insurmountable.) -Barry

ZEO is a component that lets clients on remote machines access a ZODB storage using a custon Python RMI. (The details aren't exactly important, except that it's a concrete example.) IOW, ZEO + ZODB is a client-server object database.
It's possible for all sorts of things to go wrong for an individual RMI. There can be a problem marshalling or unmarshalling the objects. There can be a problem with executing the actual methods on the server. There can be a problem with application code that is executed as a result of calling some method on the server. Etc.
Few of these errors are fatal. Some of them may be uninteresting most of the time. So I'd like to log tracebacks and vary the log level depending on the importance of the event that caused the exception.
One particular example is a KeyError. The server will raise a KeyError if the client asks for an object that doesn't exist. There are clients that catch this error on the client side and handle it cleanly. It's not an important exception for them. But I'd like to log the traceback on the server to handle cases where the client isn't expecting it. I'd probably log this at debug() level. Most of the time, nobody cares about a KeyError. But it might occasionally be useful when debugging something.
If this use case is rare enough, maybe a better approach would be to let you format it yourself using the traceback module? --Guido van Rossum (home page: http://www.python.org/~guido/)

"GvR" == Guido van Rossum <guido@python.org> writes:
GvR> If this use case is rare enough, maybe a better approach GvR> would be to let you format it yourself using the traceback GvR> module? This reminds me of another use case for logging, which may or may not be covered by PEP 282. When an uncaught exception happens in Mailman (i.e. it percolates up to the "main loop" of the current process), I always want to log it somewhere because it definitely indicates a bug. If this exception happens in the web interface, I will usually log the exception in some file, and also format the exception into html for output in the browser, /except/ when a `stealth mode' is enabled. The traceback can be considered sensitive information, so when debugging we turn stealth mode off, but we always ship with stealth mode on. In stealth mode, we still always log the traceback to a file, but we never output the traceback onto the web page. It would also be great if I can create a logger that knows how to tee its output to multiple sinks. If I understand the PEP correctly, I think I would use two standard formatters, a plaintext formatter and an html formatter. I'd then have two custom handlers, one which outputs to html mindful of the current stealth mode, and one which tees the LogRecord to two sub-handlers (i.e. the stealth-mode-aware handler, and the normal to-file handler). Does this seem reasonable given PEP 282's current proposal? -Barry

[Barry Warsaw wrote]
This reminds me of another use case for logging, which may or may not be covered by PEP 282.
When an uncaught exception happens in Mailman (i.e. it percolates up to the "main loop" of the current process), I always want to log it somewhere because it definitely indicates a bug.
If this exception happens in the web interface, I will usually log the exception in some file, and also format the exception into html for output in the browser, /except/ when a `stealth mode' is enabled. The traceback can be considered sensitive information, so when debugging we turn stealth mode off, but we always ship with stealth mode on. In stealth mode, we still always log the traceback to a file, but we never output the traceback onto the web page.
It would also be great if I can create a logger that knows how to tee its output to multiple sinks.
For sure. A Logger object maintains a list of Handlers. You could have one WebInterfaceHandler and one FileHandler attached to your Logger.
If I understand the PEP correctly, I think I would use two standard formatters, a plaintext formatter and an html formatter. I'd then have two custom handlers, one which outputs to html mindful of the current stealth mode, and one which tees the LogRecord to two sub-handlers (i.e. the stealth-mode-aware handler, and the normal to-file handler).
I don't know if I understand the example case here: do you not even log _to file_ in stealth mode? In any case I would handle this by attaching a Filter object to any Handler that should *not* log in stealth mode: class StealthModeFilter(logging.Filter): """Note this is using the Filter interface in the version of the PEP that I haven't re-released yet.""" def filter(self, record): if record.exc_info and inStealthMode(): return None # drop the record else: return record
Does this seem reasonable given PEP 282's current proposal?
Sure. It depends on how you lay out your code whether the formatting and display of the web interface is done via the logging module. I don't think that that would be the right fit. The logging to file definitely fits into the logging module's purview. Trent -- Trent Mick TrentM@ActiveState.com

"TM" == Trent Mick <trentm@ActiveState.com> writes:
TM> I don't know if I understand the example case here: do you not TM> even log _to file_ in stealth mode? No (I think that's a quintuple negative :). We always log to file, even in stealth mode. TM> In any case I would handle this by attaching a Filter object TM> to any Handler that should *not* log in stealth mode: Seems reasonable. >> Does this seem reasonable given PEP 282's current proposal? TM> Sure. It depends on how you lay out your code whether the TM> formatting and display of the web interface is done via the TM> logging module. I don't think that that would be the right TM> fit. The logging to file definitely fits into the logging TM> module's purview. Hmm, I think I'll have to grag the sample implementation and play around with it. Thanks, -Barry
participants (18)
-
Aahz
-
Barry Scott
-
barry@zope.com
-
Charles Cazabon
-
Eric S. Raymond
-
Gordon McMillan
-
Greg Ewing
-
Guido van Rossum
-
Jeremy Hylton
-
jeremy@zope.com
-
Jim Fulton
-
Mark Hammond
-
Matthias Urlichs
-
Neal Norwitz
-
Skip Montanaro
-
Tim Peters
-
Trent Mick
-
Vinay Sajip