2002-02-14 23:09:17 -05:00
|
|
|
|
PEP: 282
|
|
|
|
|
Title: A Logging System
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
2002-06-23 19:58:18 -04:00
|
|
|
|
Author: vinay_sajip@red-dove.com (Vinay Sajip)
|
2002-02-14 23:09:17 -05:00
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Created: 4-Feb-2002
|
|
|
|
|
Python-Version: 2.3
|
|
|
|
|
Post-History:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
|
|
|
|
|
This PEP describes a proposed logging package for Python's
|
|
|
|
|
standard library.
|
|
|
|
|
|
|
|
|
|
Basically the system involves the user creating one or more
|
|
|
|
|
logging objects on which methods are called to log debugging
|
|
|
|
|
notes/general information/warnings/errors/etc. Different logging
|
|
|
|
|
'levels' can be used to distinguish important messages from
|
|
|
|
|
trivial ones.
|
|
|
|
|
|
|
|
|
|
A registry of named singleton logger objects is maintained so that
|
|
|
|
|
|
|
|
|
|
1) different logical logging streams (or 'channels') exist
|
|
|
|
|
(say, one for 'zope.zodb' stuff and another for
|
|
|
|
|
'mywebsite'-specific stuff)
|
|
|
|
|
|
|
|
|
|
2) one does not have to pass logger object references around.
|
|
|
|
|
|
|
|
|
|
The system is configurable at runtime. This configuration
|
|
|
|
|
mechanism allows one to tune the level and type of logging done
|
|
|
|
|
while not touching the application itself.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
|
|
|
|
|
|
If a single logging mechanism is enshrined in the standard
|
|
|
|
|
library, 1) logging is more likely to be done 'well', and 2)
|
|
|
|
|
multiple libraries will be able to be integrated into larger
|
|
|
|
|
applications which can be logged reasonably coherently.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Influences
|
|
|
|
|
|
|
|
|
|
This proposal was put together after having somewhat studied the
|
|
|
|
|
following logging packages:
|
|
|
|
|
|
|
|
|
|
o java.util.logging in JDK 1.4 (a.k.a. JSR047) [1]
|
|
|
|
|
o log4j [2]
|
|
|
|
|
These two systems are *very* similar.
|
|
|
|
|
o the Syslog package from the Protomatter project [3]
|
|
|
|
|
o MAL's mx.Log package [4]
|
|
|
|
|
|
|
|
|
|
This proposal will basically look like java.util.logging with a
|
|
|
|
|
smattering of log4j.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Simple Example
|
|
|
|
|
|
|
|
|
|
This shows a very simple example of how the logging package can be
|
|
|
|
|
used to generate simple logging output on stdout.
|
|
|
|
|
|
|
|
|
|
--------- mymodule.py -------------------------------
|
|
|
|
|
import logging
|
|
|
|
|
log = logging.getLogger("MyModule")
|
|
|
|
|
|
|
|
|
|
def doit():
|
|
|
|
|
log.debug("doin' stuff")
|
|
|
|
|
# do stuff ...
|
|
|
|
|
-----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
--------- myapp.py ----------------------------------
|
|
|
|
|
import mymodule, logging
|
|
|
|
|
log = logging.getLogger("MyApp")
|
|
|
|
|
|
|
|
|
|
log.info("start my app")
|
|
|
|
|
try:
|
|
|
|
|
mymodule.doit()
|
|
|
|
|
except Exception, e:
|
|
|
|
|
log.error("There was a problem doin' stuff.")
|
|
|
|
|
log.info("end my app")
|
|
|
|
|
-----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
> python myapp.py
|
|
|
|
|
0 [myapp.py:4] INFO MyApp - start my app
|
|
|
|
|
36 [mymodule.py:5] DEBUG MyModule - doin' stuff
|
|
|
|
|
51 [myapp.py:9] INFO MyApp - end my app
|
|
|
|
|
^^ ^^^^^^^^^^^^ ^^^^ ^^^^^ ^^^^^^^^^^
|
|
|
|
|
| | | | `-- message
|
|
|
|
|
| | | `-- logging name/channel
|
|
|
|
|
| | `-- level
|
|
|
|
|
| `-- location
|
|
|
|
|
`-- time
|
|
|
|
|
|
|
|
|
|
NOTE: Not sure exactly what the default format will look like yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Control Flow
|
|
|
|
|
|
|
|
|
|
[Note: excerpts from Java Logging Overview. [5]]
|
|
|
|
|
|
|
|
|
|
Applications make logging calls on *Logger* objects. Loggers are
|
|
|
|
|
organized in a hierarchical namespace and child Loggers may
|
|
|
|
|
inherit some logging properties from their parents in the
|
|
|
|
|
namespace.
|
|
|
|
|
|
|
|
|
|
Notes on namespace: Logger names fit into a "dotted name"
|
|
|
|
|
namespace, with dots (periods) indicating sub-namespaces. The
|
|
|
|
|
namespace of logger objects therefore corresponds to a single tree
|
|
|
|
|
data structure.
|
|
|
|
|
|
|
|
|
|
"" is the root of the namespace
|
|
|
|
|
"Zope" would be a child node of the root
|
|
|
|
|
"Zope.ZODB" would be a child node of "Zope"
|
|
|
|
|
|
|
|
|
|
These Logger objects allocate *LogRecord* objects which are passed
|
|
|
|
|
to *Handler* objects for publication. Both Loggers and Handlers
|
|
|
|
|
may use logging *levels* and (optionally) *Filters* to decide if
|
|
|
|
|
they are interested in a particular LogRecord. When it is
|
|
|
|
|
necessary to publish a LogRecord externally, a Handler can
|
|
|
|
|
(optionally) use a *Formatter* to localize and format the message
|
|
|
|
|
before publishing it to an I/O stream.
|
|
|
|
|
|
|
|
|
|
Each Logger keeps track of a set of output Handlers. By default
|
|
|
|
|
all Loggers also send their output to their parent Logger. But
|
|
|
|
|
Loggers may also be configured to ignore Handlers higher up the
|
|
|
|
|
tree.
|
|
|
|
|
|
|
|
|
|
The APIs are structured so that calls on the Logger APIs can be
|
|
|
|
|
cheap when logging is disabled. If logging is disabled for a
|
|
|
|
|
given log level, then the Logger can make a cheap comparison test
|
|
|
|
|
and return. If logging is enabled for a given log level, the
|
|
|
|
|
Logger is still careful to minimize costs before passing the
|
|
|
|
|
LogRecord into the Handlers. In particular, localization and
|
|
|
|
|
formatting (which are relatively expensive) are deferred until the
|
|
|
|
|
Handler requests them.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Levels
|
|
|
|
|
|
|
|
|
|
The logging levels, in increasing order of importance, are:
|
|
|
|
|
|
|
|
|
|
DEBUG
|
|
|
|
|
INFO
|
|
|
|
|
WARN
|
|
|
|
|
ERROR
|
|
|
|
|
FATAL
|
|
|
|
|
ALL
|
|
|
|
|
|
|
|
|
|
This is consistent with log4j and Protomatter's Syslog and not
|
|
|
|
|
with JSR047 which has a few more levels and some different names.
|
|
|
|
|
|
|
|
|
|
Implementation-wise: these are just integer constants, to allow
|
|
|
|
|
simple comparison of importance. See "What Logging Levels?" below
|
|
|
|
|
for a debate on what standard levels should be defined.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Loggers
|
|
|
|
|
|
|
|
|
|
Each Logger object keeps track of a log level (or threshold) that
|
|
|
|
|
it is interested in, and discards log requests below that level.
|
|
|
|
|
|
|
|
|
|
The *LogManager* maintains a hierarchical namespace of named
|
|
|
|
|
Logger objects. Generations are denoted with dot-separated names:
|
|
|
|
|
Logger "foo" is the parent of Loggers "foo.bar" and "foo.baz".
|
|
|
|
|
|
|
|
|
|
The main logging method is:
|
|
|
|
|
|
|
|
|
|
class Logger:
|
|
|
|
|
def log(self, level, msg, *args):
|
|
|
|
|
"""Log 'msg % args' at logging level 'level'."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
however convenience functions are defined for each logging level:
|
|
|
|
|
|
|
|
|
|
def debug(self, msg, *args): ...
|
|
|
|
|
def info(self, msg, *args): ...
|
|
|
|
|
def warn(self, msg, *args): ...
|
|
|
|
|
def error(self, msg, *args): ...
|
|
|
|
|
def fatal(self, msg, *args): ...
|
|
|
|
|
|
|
|
|
|
XXX How to defined a nice convenience function for logging an exception?
|
|
|
|
|
mx.Log has something like this, doesn't it?
|
|
|
|
|
|
|
|
|
|
XXX What about a .raising() convenience function? How about:
|
|
|
|
|
|
|
|
|
|
def raising(self, exception, level=ERROR): ...
|
|
|
|
|
|
|
|
|
|
It would create a log message describing an exception that is
|
|
|
|
|
about to be raised. I don't like that 'level' is not first
|
|
|
|
|
when it *is* first for .log().
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Handlers
|
|
|
|
|
|
|
|
|
|
Handlers are responsible for doing something useful with a given
|
|
|
|
|
LogRecord. The following core Handlers will be implemented:
|
|
|
|
|
|
|
|
|
|
- StreamHandler: A handler for writing to a file-like object.
|
|
|
|
|
- FileHandler: A handler for writing to a single file or set
|
|
|
|
|
of rotating files.
|
|
|
|
|
|
|
|
|
|
More standard Handlers may be implemented if deemed desirable and
|
|
|
|
|
feasible. Other interesting candidates:
|
|
|
|
|
|
|
|
|
|
- SocketHandler: A handler for writing to remote TCP ports.
|
|
|
|
|
- CreosoteHandler: A handler for writing to UDP packets, for
|
|
|
|
|
low-cost logging. Jeff Bauer already had such a system [5].
|
|
|
|
|
- MemoryHandler: A handler that buffers log records in memory
|
|
|
|
|
(JSR047).
|
|
|
|
|
- SMTPHandler: Akin to log4j's SMTPAppender.
|
|
|
|
|
- SyslogHandler: Akin to log4j's SyslogAppender.
|
|
|
|
|
- NTEventLogHandler: Akin to log4j's NTEventLogAppender.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Formatters
|
|
|
|
|
|
|
|
|
|
A Formatter is responsible for converting a LogRecord to a string
|
|
|
|
|
representation. A Handler may call its Formatter before writing a
|
|
|
|
|
record. The following core Formatters will be implemented:
|
|
|
|
|
|
|
|
|
|
- Formatter: Provide printf-like formatting, perhaps akin to
|
|
|
|
|
log4j's PatternAppender.
|
|
|
|
|
|
|
|
|
|
Other possible candidates for implementation:
|
|
|
|
|
|
|
|
|
|
- XMLFormatter: Serialize a LogRecord according to a specific
|
|
|
|
|
schema. Could copy the schema from JSR047's XMLFormatter or
|
|
|
|
|
log4j's XMLAppender.
|
|
|
|
|
- HTMLFormatter: Provide a simple HTML output of log
|
|
|
|
|
information. (See log4j's HTMLAppender.)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Filters
|
|
|
|
|
|
|
|
|
|
A Filter can be called by a Logger or Handler to decide if a
|
|
|
|
|
LogRecord should be logged.
|
|
|
|
|
|
|
|
|
|
JSR047 and log4j have slightly different filtering interfaces. The
|
|
|
|
|
former is simpler:
|
|
|
|
|
|
|
|
|
|
class Filter:
|
|
|
|
|
def isLoggable(self):
|
|
|
|
|
"""Return a boolean."""
|
|
|
|
|
|
|
|
|
|
The latter is modeled after Linux's ipchains (where Filter's can
|
|
|
|
|
be chained with each filter either 'DENY'ing, 'ACCEPT'ing, or
|
|
|
|
|
being 'NEUTRAL' on each check). I would probably favor to former
|
|
|
|
|
because it is simpler and I don't immediate see the need for the
|
|
|
|
|
latter.
|
|
|
|
|
|
|
|
|
|
No filter implementations are currently proposed (other that the
|
|
|
|
|
do nothing base class) because I don't have enough experience to
|
|
|
|
|
know what kinds of filters would be common. Users can always
|
|
|
|
|
subclass Filter for their own purposes. Log4j includes a few
|
|
|
|
|
filters that might be interesting.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Configuration
|
|
|
|
|
|
|
|
|
|
Note: Configuration for the proposed logging system is currently
|
|
|
|
|
under-specified.
|
|
|
|
|
|
|
|
|
|
The main benefit of a logging system like this is that one can
|
|
|
|
|
control how much and what logging output one gets from an
|
|
|
|
|
application without changing that application's source code.
|
|
|
|
|
|
|
|
|
|
Log4j and Syslog provide for configuration via an external XML
|
|
|
|
|
file. Log4j and JSR047 provide for configuration via Java
|
|
|
|
|
properties (similar to -D #define's to a C/C++ compiler). All
|
|
|
|
|
three provide for configuration via API calls.
|
|
|
|
|
|
|
|
|
|
Configuration includes the following:
|
|
|
|
|
|
|
|
|
|
- What logging level a logger should be interested in.
|
|
|
|
|
- What handlers should be attached to which loggers.
|
|
|
|
|
- What filters should be attached to which handlers and loggers.
|
|
|
|
|
- Specifying attributes specific to certain Handlers and Filters.
|
|
|
|
|
- Defining the default configuration.
|
|
|
|
|
- XXX Add others.
|
|
|
|
|
|
|
|
|
|
In general each application will have its own requirements for how
|
|
|
|
|
a user may configure logging output. One application
|
|
|
|
|
(e.g. distutils) may want to control logging levels via
|
|
|
|
|
'-q,--quiet,-v,--verbose' options to setup.py. Zope may want to
|
|
|
|
|
configure logging via certain environment variables
|
|
|
|
|
(e.g. 'STUPID_LOG_FILE' :). Komodo may want to configure logging
|
|
|
|
|
via its preferences system.
|
|
|
|
|
|
|
|
|
|
This PEP proposes to clearly document the API for configuring each
|
|
|
|
|
of the above listed configurable elements and to define a
|
|
|
|
|
reasonable default configuration. This PEP does not propose to
|
|
|
|
|
define a general XML or .ini file configuration schema and the
|
|
|
|
|
backend to parse it.
|
|
|
|
|
|
|
|
|
|
It might, however, be worthwhile to define an abstraction of the
|
|
|
|
|
configuration API to allow the expressiveness of Syslog
|
|
|
|
|
configuration. Greg Wilson made this argument:
|
|
|
|
|
|
|
|
|
|
In Protomatter [Syslog], you configure by saying "give me
|
|
|
|
|
everything that matches these channel+level combinations",
|
|
|
|
|
such as "server.error" and "database.*". The log4j "configure
|
|
|
|
|
by inheritance" model, on the other hand, is very clever, but
|
|
|
|
|
hard for non-programmers to manage without a GUI that
|
|
|
|
|
essentially reduces it to Protomatter's.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Case Scenarios
|
|
|
|
|
|
|
|
|
|
This section presents a few usage scenarios which will be used to
|
|
|
|
|
help decide how best to specify the logging API.
|
|
|
|
|
|
|
|
|
|
(1) A short simple script.
|
|
|
|
|
|
|
|
|
|
This script does not have many lines. It does not heavily use
|
|
|
|
|
any third party modules (i.e. the only code doing any logging
|
|
|
|
|
would be the main script). Only one logging channel is really
|
|
|
|
|
needed and thus, the channel name is unnecessary. The user
|
|
|
|
|
doesn't want to bother with logging system configuration much.
|
|
|
|
|
|
|
|
|
|
(2) Medium sized app with C extension module.
|
|
|
|
|
|
|
|
|
|
Includes a few Python modules and a main script. Employs,
|
|
|
|
|
perhaps, a few logging channels. Includes a C extension
|
|
|
|
|
module which might want to make logging calls as well.
|
|
|
|
|
|
|
|
|
|
(3) Distutils.
|
|
|
|
|
|
|
|
|
|
A large number of Python packages/modules. Perhaps (but not
|
|
|
|
|
necessarily) a number of logging channels are used.
|
|
|
|
|
Specifically needs to facilitate the controlling verbosity
|
|
|
|
|
levels via simple command line options to 'setup.py'.
|
|
|
|
|
|
|
|
|
|
(4) Large, possibly multi-language, app. E.g. Zope or (my
|
|
|
|
|
experience) Komodo.
|
|
|
|
|
|
|
|
|
|
(I don't expect this logging system to deal with any
|
|
|
|
|
cross-language issues but it is something to think about.)
|
|
|
|
|
Many channels are used. Many developers involved. People
|
|
|
|
|
providing user support are possibly not the same people who
|
|
|
|
|
developed the application. Users should be able to generate
|
|
|
|
|
log files (i.e. configure logging) while reproducing a bug to
|
|
|
|
|
send back to developers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
|
|
|
|
|
XXX Details to follow consensus that this proposal is a good idea.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
What Logging Levels?
|
|
|
|
|
|
|
|
|
|
The following are the logging levels defined by the systems I looked at:
|
|
|
|
|
|
|
|
|
|
- log4j: DEBUG, INFO, WARN, ERROR, FATAL
|
|
|
|
|
- syslog: DEBUG, INFO, WARNING, ERROR, FATAL
|
|
|
|
|
- JSR047: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE
|
|
|
|
|
- zLOG (used by Zope):
|
|
|
|
|
TRACE=-300 -- Trace messages
|
|
|
|
|
DEBUG=-200 -- Debugging messages
|
|
|
|
|
BLATHER=-100 -- Somebody shut this app up.
|
|
|
|
|
INFO=0 -- For things like startup and shutdown.
|
|
|
|
|
PROBLEM=100 -- This isn't causing any immediate problems, but
|
|
|
|
|
deserves attention.
|
|
|
|
|
WARNING=100 -- A wishy-washy alias for PROBLEM.
|
|
|
|
|
ERROR=200 -- This is going to have adverse effects.
|
|
|
|
|
PANIC=300 -- We're dead!
|
|
|
|
|
- mx.Log:
|
|
|
|
|
SYSTEM_DEBUG
|
|
|
|
|
SYSTEM_INFO
|
|
|
|
|
SYSTEM_UNIMPORTANT
|
|
|
|
|
SYSTEM_MESSAGE
|
|
|
|
|
SYSTEM_WARNING
|
|
|
|
|
SYSTEM_IMPORTANT
|
|
|
|
|
SYSTEM_CANCEL
|
|
|
|
|
SYSTEM_ERROR
|
|
|
|
|
SYSTEM_PANIC
|
|
|
|
|
SYSTEM_FATAL
|
|
|
|
|
|
|
|
|
|
The current proposal is to copy log4j. XXX I suppose I could see
|
|
|
|
|
adding zLOG's "TRACE" level, but I am not sure of the usefulness
|
|
|
|
|
of others.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Static Logging Methods (as per Syslog)?
|
|
|
|
|
|
|
|
|
|
Both zLOG and Syslog provide module-level logging functions rather
|
|
|
|
|
(or in addition to) logging methods on a created Logger object.
|
|
|
|
|
XXX Is this something that is deemed worth including?
|
|
|
|
|
|
|
|
|
|
Pros:
|
|
|
|
|
- It would make the simplest case shorter:
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
logging.error("Something is wrong")
|
|
|
|
|
|
|
|
|
|
instead of
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
log = logging.getLogger("")
|
|
|
|
|
log.error("Something is wrong")
|
|
|
|
|
|
|
|
|
|
Cons:
|
|
|
|
|
- It provides more than one way to do it.
|
|
|
|
|
- It encourages logging without a channel name, because this
|
|
|
|
|
mechanism would likely be implemented by implicitly logging
|
|
|
|
|
on the root (and nameless) logger of the hierarchy.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
|
|
|
|
|
[1] java.util.logging
|
|
|
|
|
http://java.sun.com/j2se/1.4/docs/guide/util/logging/
|
|
|
|
|
|
|
|
|
|
[2] log4j: a Java logging package
|
|
|
|
|
http://jakarta.apache.org/log4j/docs/index.html
|
|
|
|
|
|
|
|
|
|
[3] Protomatter's Syslog
|
|
|
|
|
http://protomatter.sourceforge.net/1.1.6/index.html
|
|
|
|
|
http://protomatter.sourceforge.net/1.1.6/javadoc/com/protomatter/syslog/syslog-whitepaper.html
|
|
|
|
|
|
|
|
|
|
[4] MAL mentions his mx.Log logging module:
|
|
|
|
|
http://mail.python.org/pipermail/python-dev/2002-February/019767.html
|
|
|
|
|
|
|
|
|
|
[5] Jeff Bauer's Mr. Creosote
|
|
|
|
|
http://starship.python.net/crew/jbauer/creosote/
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
fill-column: 70
|
|
|
|
|
End:
|