reSTify PEP 282 (#358)

This commit is contained in:
Huang Huang 2017-08-22 01:07:20 +08:00 committed by Brett Cannon
parent a5101b1988
commit f22e81b255
1 changed files with 441 additions and 411 deletions

View File

@ -2,16 +2,17 @@ PEP: 282
Title: A Logging System Title: A Logging System
Version: $Revision$ Version: $Revision$
Last-Modified: $Date$ Last-Modified: $Date$
Author: vinay_sajip at red-dove.com (Vinay Sajip), Author: vinay_sajip at red-dove.com (Vinay Sajip), trentm@activestate.com (Trent Mick)
trentm@activestate.com (Trent Mick)
Status: Final Status: Final
Type: Standards Track Type: Standards Track
Content-Type: text/x-rst
Created: 4-Feb-2002 Created: 4-Feb-2002
Python-Version: 2.3 Python-Version: 2.3
Post-History: Post-History:
Abstract Abstract
========
This PEP describes a proposed logging package for Python's This PEP describes a proposed logging package for Python's
standard library. standard library.
@ -36,6 +37,7 @@ Abstract
Motivation Motivation
==========
If a single logging mechanism is enshrined in the standard If a single logging mechanism is enshrined in the standard
library, 1) logging is more likely to be done 'well', and 2) library, 1) logging is more likely to be done 'well', and 2)
@ -44,21 +46,25 @@ Motivation
Influences Influences
==========
This proposal was put together after having studied the This proposal was put together after having studied the
following logging packages: following logging packages:
o java.util.logging in JDK 1.4 (a.k.a. JSR047) [1] * java.util.logging in JDK 1.4 (a.k.a. JSR047) [1]_
o log4j [2] * log4j [2]_
o the Syslog package from the Protomatter project [3] * the Syslog package from the Protomatter project [3]_
o MAL's mx.Log package [4] * MAL's mx.Log package [4]_
Simple Example Simple Example
==============
This shows a very simple example of how the logging package can be This shows a very simple example of how the logging package can be
used to generate simple logging output on stderr. used to generate simple logging output on stderr.
::
--------- mymodule.py ------------------------------- --------- mymodule.py -------------------------------
import logging import logging
log = logging.getLogger("MyModule") log = logging.getLogger("MyModule")
@ -100,7 +106,7 @@ Simple Example
The above example shows the default output format. All The above example shows the default output format. All
aspects of the output format should be configurable, so that aspects of the output format should be configurable, so that
you could have output formatted like this: you could have output formatted like this::
2002-04-19 07:56:58,174 MyModule DEBUG - Doin' stuff... 2002-04-19 07:56:58,174 MyModule DEBUG - Doin' stuff...
@ -110,8 +116,9 @@ Simple Example
Control Flow Control Flow
============
Applications make logging calls on *Logger* objects. Loggers are Applications make logging calls on **Logger** objects. Loggers are
organized in a hierarchical namespace and child Loggers inherit organized in a hierarchical namespace and child Loggers inherit
some logging properties from their parents in the namespace. some logging properties from their parents in the namespace.
@ -119,16 +126,18 @@ Control Flow
(periods) indicating sub-namespaces. The namespace of logger (periods) indicating sub-namespaces. The namespace of logger
objects therefore corresponds to a single tree data structure. objects therefore corresponds to a single tree data structure.
::
"" is the root of the namespace "" is the root of the namespace
"Zope" would be a child node of the root "Zope" would be a child node of the root
"Zope.ZODB" would be a child node of "Zope" "Zope.ZODB" would be a child node of "Zope"
These Logger objects create *LogRecord* objects which are passed These Logger objects create **LogRecord** objects which are passed
to *Handler* objects for output. Both Loggers and Handlers may to **Handler** objects for output. Both Loggers and Handlers may
use logging *levels* and (optionally) *Filters* to decide if they use logging **levels** and (optionally) **Filters** to decide if they
are interested in a particular LogRecord. When it is necessary to are interested in a particular LogRecord. When it is necessary to
output a LogRecord externally, a Handler can (optionally) use a output a LogRecord externally, a Handler can (optionally) use a
*Formatter* to localize and format the message before sending it **Formatter** to localize and format the message before sending it
to an I/O stream. to an I/O stream.
Each Logger keeps track of a set of output Handlers. By default Each Logger keeps track of a set of output Handlers. By default
@ -147,7 +156,7 @@ Control Flow
The overall Logger hierarchy can also have a level associated with The overall Logger hierarchy can also have a level associated with
it, which takes precedence over the levels of individual Loggers. it, which takes precedence over the levels of individual Loggers.
This is done through a module-level function: This is done through a module-level function::
def disable(lvl): def disable(lvl):
""" """
@ -158,8 +167,9 @@ Control Flow
Levels Levels
======
The logging levels, in increasing order of importance, are: The logging levels, in increasing order of importance, are::
DEBUG DEBUG
INFO INFO
@ -188,7 +198,7 @@ Levels
increasing severity. increasing severity.
User-defined logging levels are supported through two module-level User-defined logging levels are supported through two module-level
functions: functions::
def getLevelName(lvl): def getLevelName(lvl):
"""Return the text for level 'lvl'.""" """Return the text for level 'lvl'."""
@ -202,11 +212,12 @@ Levels
... ...
Loggers Loggers
=======
Each Logger object keeps track of a log level (or threshold) that Each Logger object keeps track of a log level (or threshold) that
it is interested in, and discards log requests below that level. it is interested in, and discards log requests below that level.
A *Manager* class instance maintains the hierarchical namespace of A **Manager** class instance maintains the hierarchical namespace of
named Logger objects. Generations are denoted with dot-separated named Logger objects. Generations are denoted with dot-separated
names: Logger "foo" is the parent of Loggers "foo.bar" and names: Logger "foo" is the parent of Loggers "foo.bar" and
"foo.baz". "foo.baz".
@ -215,14 +226,14 @@ Loggers
exposed to users, who interact with it using various module-level exposed to users, who interact with it using various module-level
functions. functions.
The general logging method is: The general logging method is::
class Logger: class Logger:
def log(self, lvl, msg, *args, **kwargs): def log(self, lvl, msg, *args, **kwargs):
"""Log 'str(msg) % args' at logging level 'lvl'.""" """Log 'str(msg) % args' at logging level 'lvl'."""
... ...
However, convenience functions are defined for each logging level: However, convenience functions are defined for each logging level::
class Logger: class Logger:
def debug(self, msg, *args, **kwargs): ... def debug(self, msg, *args, **kwargs): ...
@ -234,10 +245,10 @@ Loggers
Only one keyword argument is recognized at present - "exc_info". Only one keyword argument is recognized at present - "exc_info".
If true, the caller wants exception information to be provided in If true, the caller wants exception information to be provided in
the logging output. This mechanism is only needed if exception the logging output. This mechanism is only needed if exception
information needs to be provided at *any* logging level. In the information needs to be provided at **any** logging level. In the
more common case, where exception information needs to be added to more common case, where exception information needs to be added to
the log only when errors occur, i.e. at the ERROR level, then the log only when errors occur, i.e. at the ERROR level, then
another convenience method is provided: another convenience method is provided::
class Logger: class Logger:
def exception(self, msg, *args): ... def exception(self, msg, *args): ...
@ -250,11 +261,11 @@ Loggers
context of an INFO message, for example. context of an INFO message, for example.
The "msg" argument shown above will normally be a format string; The "msg" argument shown above will normally be a format string;
however, it can be any object x for which str(x) returns the however, it can be any object x for which ``str(x)`` returns the
format string. This facilitates, for example, the use of an format string. This facilitates, for example, the use of an
object which fetches a locale- specific message for an object which fetches a locale- specific message for an
internationalized/localized application, perhaps using the internationalized/localized application, perhaps using the
standard gettext module. An outline example: standard gettext module. An outline example::
class Message: class Message:
"""Represents a message""" """Represents a message"""
@ -271,7 +282,7 @@ Loggers
Gathering and formatting data for a log message may be expensive, Gathering and formatting data for a log message may be expensive,
and a waste if the logger was going to discard the message anyway. and a waste if the logger was going to discard the message anyway.
To see if a request will be honoured by the logger, the To see if a request will be honoured by the logger, the
isEnabledFor() method can be used: ``isEnabledFor()`` method can be used::
class Logger: class Logger:
def isEnabledFor(self, lvl): def isEnabledFor(self, lvl):
@ -282,14 +293,14 @@ Loggers
... ...
so instead of this expensive and possibly wasteful DOM to XML so instead of this expensive and possibly wasteful DOM to XML
conversion: conversion::
... ...
hamletStr = hamletDom.toxml() hamletStr = hamletDom.toxml()
log.info(hamletStr) log.info(hamletStr)
... ...
one can do this: one can do this::
if log.isEnabledFor(logging.INFO): if log.isEnabledFor(logging.INFO):
hamletStr = hamletDom.toxml() hamletStr = hamletDom.toxml()
@ -297,7 +308,7 @@ Loggers
When new loggers are created, they are initialized with a level When new loggers are created, they are initialized with a level
which signifies "no level". A level can be set explicitly using which signifies "no level". A level can be set explicitly using
the setLevel() method: the ``setLevel()`` method::
class Logger: class Logger:
def setLevel(self, lvl): ... def setLevel(self, lvl): ...
@ -305,12 +316,12 @@ Loggers
If a logger's level is not set, the system consults all its If a logger's level is not set, the system consults all its
ancestors, walking up the hierarchy until an explicitly set level ancestors, walking up the hierarchy until an explicitly set level
is found. That is regarded as the "effective level" of the is found. That is regarded as the "effective level" of the
logger, and can be queried via the getEffectiveLevel() method: logger, and can be queried via the getEffectiveLevel() method::
def getEffectiveLevel(self): ... def getEffectiveLevel(self): ...
Loggers are never instantiated directly. Instead, a module-level Loggers are never instantiated directly. Instead, a module-level
function is used: function is used::
def getLogger(name=None): ... def getLogger(name=None): ...
@ -320,37 +331,38 @@ Loggers
with "channel name". with "channel name".
Users can specify a custom subclass of Logger to be used by the Users can specify a custom subclass of Logger to be used by the
system when instantiating new loggers: system when instantiating new loggers::
def setLoggerClass(klass): ... def setLoggerClass(klass): ...
The passed class should be a subclass of Logger, and its __init__ The passed class should be a subclass of Logger, and its ``__init__``
method should call Logger.__init__. method should call ``Logger.__init__``.
Handlers Handlers
========
Handlers are responsible for doing something useful with a given Handlers are responsible for doing something useful with a given
LogRecord. The following core Handlers will be implemented: ``LogRecord``. The following core Handlers will be implemented:
- StreamHandler: A handler for writing to a file-like object. - ``StreamHandler``: A handler for writing to a file-like object.
- FileHandler: A handler for writing to a single file or set - ``FileHandler``: A handler for writing to a single file or set
of rotating files. of rotating files.
- SocketHandler: A handler for writing to remote TCP ports. - ``SocketHandler``: A handler for writing to remote TCP ports.
- DatagramHandler: A handler for writing to UDP sockets, for - ``DatagramHandler``: A handler for writing to UDP sockets, for
low-cost logging. Jeff Bauer already had such a system [5]. low-cost logging. Jeff Bauer already had such a system [5]_.
- MemoryHandler: A handler that buffers log records in memory - ``MemoryHandler``: A handler that buffers log records in memory
until the buffer is full or a particular condition occurs until the buffer is full or a particular condition occurs
[1]. [1]_.
- SMTPHandler: A handler for sending to email addresses via SMTP. - ``SMTPHandler``: A handler for sending to email addresses via SMTP.
- SysLogHandler: A handler for writing to Unix syslog via UDP. - ``SysLogHandler``: A handler for writing to Unix syslog via UDP.
- NTEventLogHandler: A handler for writing to event logs on - ``NTEventLogHandler``: A handler for writing to event logs on
Windows NT, 2000 and XP. Windows NT, 2000 and XP.
- HTTPHandler: A handler for writing to a Web server with - ``HTTPHandler``: A handler for writing to a Web server with
either GET or POST semantics. either GET or POST semantics.
Handlers can also have levels set for them using the Handlers can also have levels set for them using the
setLevel() method: ``setLevel()`` method::
def setLevel(self, lvl): ... def setLevel(self, lvl): ...
@ -363,6 +375,8 @@ Handlers
The setRollover method is used to specify a maximum size for a log The setRollover method is used to specify a maximum size for a log
file and a maximum number of backup files in the rotation. file and a maximum number of backup files in the rotation.
::
def setRollover(maxBytes, backupCount): ... def setRollover(maxBytes, backupCount): ...
If maxBytes is specified as zero, no rollover ever occurs and the If maxBytes is specified as zero, no rollover ever occurs and the
@ -373,72 +387,76 @@ Handlers
that, and so on. that, and so on.
There are many additional handlers implemented in the test/example There are many additional handlers implemented in the test/example
scripts provided with [6] - for example, XMLHandler and scripts provided with [6]_ - for example, XMLHandler and
SOAPHandler. SOAPHandler.
LogRecords LogRecords
==========
A LogRecord acts as a receptacle for information about a A LogRecord acts as a receptacle for information about a
logging event. It is little more than a dictionary, though it logging event. It is little more than a dictionary, though it
does define a getMessage method which merges a message with does define a ``getMessage`` method which merges a message with
optional runarguments. optional runarguments.
Formatters Formatters
==========
A Formatter is responsible for converting a LogRecord to a string A Formatter is responsible for converting a LogRecord to a string
representation. A Handler may call its Formatter before writing a representation. A Handler may call its Formatter before writing a
record. The following core Formatters will be implemented: record. The following core Formatters will be implemented:
- Formatter: Provide printf-like formatting, using the % operator. - ``Formatter``: Provide printf-like formatting, using the % operator.
- BufferingFormatter: Provide formatting for multiple - ``BufferingFormatter``: Provide formatting for multiple
messages, with header and trailer formatting support. messages, with header and trailer formatting support.
Formatters are associated with Handlers by calling setFormatter() Formatters are associated with Handlers by calling ``setFormatter()``
on a handler: on a handler::
def setFormatter(self, form): ... def setFormatter(self, form): ...
Formatters use the % operator to format the logging message. The Formatters use the % operator to format the logging message. The
format string should contain %(name)x and the attribute dictionary format string should contain ``%(name)x`` and the attribute dictionary
of the LogRecord is used to obtain message-specific data. The of the LogRecord is used to obtain message-specific data. The
following attributes are provided: following attributes are provided:
%(name)s Name of the logger (logging channel) ======================= ======================================================
``%(name)s`` Name of the logger (logging channel)
%(levelno)s Numeric logging level for the message (DEBUG, ``%(levelno)s`` Numeric logging level for the message (DEBUG,
INFO, WARN, ERROR, CRITICAL) INFO, WARN, ERROR, CRITICAL)
%(levelname)s Text logging level for the message ("DEBUG", "INFO", ``%(levelname)s`` Text logging level for the message ("DEBUG", "INFO",
"WARN", "ERROR", "CRITICAL") "WARN", "ERROR", "CRITICAL")
%(pathname)s Full pathname of the source file where the logging ``%(pathname)s`` Full pathname of the source file where the logging
call was issued (if available) call was issued (if available)
%(filename)s Filename portion of pathname ``%(filename)s`` Filename portion of pathname
%(module)s Module from which logging call was made ``%(module)s`` Module from which logging call was made
%(lineno)d Source line number where the logging call was issued ``%(lineno)d`` Source line number where the logging call was issued
(if available) (if available)
%(created)f Time when the LogRecord was created (time.time() ``%(created)f`` Time when the LogRecord was created (``time.time()``
return value) return value)
%(asctime)s Textual time when the LogRecord was created ``%(asctime)s`` Textual time when the LogRecord was created
%(msecs)d Millisecond portion of the creation time ``%(msecs)d`` Millisecond portion of the creation time
%(relativeCreated)d Time in milliseconds when the LogRecord was created, ``%(relativeCreated)d`` Time in milliseconds when the LogRecord was created,
relative to the time the logging module was loaded relative to the time the logging module was loaded
(typically at application startup time) (typically at application startup time)
%(thread)d Thread ID (if available) ``%(thread)d`` Thread ID (if available)
%(message)s The result of record.getMessage(), computed just as ``%(message)s`` The result of record.getMessage(), computed just as
the record is emitted the record is emitted
======================= ======================================================
If a formatter sees that the format string includes "(asctime)s", If a formatter sees that the format string includes "(asctime)s",
the creation time is formatted into the LogRecord's asctime the creation time is formatted into the LogRecord's asctime
@ -458,12 +476,15 @@ Formatters
Filters Filters
=======
When level-based filtering is insufficient, a Filter can be called When level-based filtering is insufficient, a Filter can be called
by a Logger or Handler to decide if a LogRecord should be output. by a Logger or Handler to decide if a LogRecord should be output.
Loggers and Handlers can have multiple filters installed, and any Loggers and Handlers can have multiple filters installed, and any
one of them can veto a LogRecord being output. one of them can veto a LogRecord being output.
::
class Filter: class Filter:
def filter(self, record): def filter(self, record):
""" """
@ -483,10 +504,11 @@ Filters
application; the focus can be changed simply by changing a filter application; the focus can be changed simply by changing a filter
attached to the root logger. attached to the root logger.
There are many examples of Filters provided in [6]. There are many examples of Filters provided in [6]_.
Configuration Configuration
=============
The main benefit of a logging system like this is that one can The main benefit of a logging system like this is that one can
control how much and what logging output one gets from an control how much and what logging output one gets from an
@ -511,9 +533,11 @@ Configuration
The most simple configuration is that of a single handler, writing The most simple configuration is that of a single handler, writing
to stderr, attached to the root logger. This configuration is set to stderr, attached to the root logger. This configuration is set
up by calling the basicConfig() function once the logging module up by calling the ``basicConfig()`` function once the logging module
has been imported. has been imported.
::
def basicConfig(): ... def basicConfig(): ...
For more sophisticated configurations, this PEP makes no specific For more sophisticated configurations, this PEP makes no specific
@ -524,12 +548,12 @@ Configuration
Python community, there is no way to know whether any given Python community, there is no way to know whether any given
configuration approach is a good one. That practice can't configuration approach is a good one. That practice can't
really come until the logging module is used, and that means really come until the logging module is used, and that means
until *after* Python 2.3 has shipped. until **after** Python 2.3 has shipped.
- There is a likelihood that different types of applications - There is a likelihood that different types of applications
may require different configuration approaches, so that no may require different configuration approaches, so that no
"one size fits all". "one size fits all".
The reference implementation [6] has a working configuration file The reference implementation [6]_ has a working configuration file
format, implemented for the purpose of proving the concept and format, implemented for the purpose of proving the concept and
suggesting one possible alternative. It may be that separate suggesting one possible alternative. It may be that separate
extension modules, not part of the core Python distribution, are extension modules, not part of the core Python distribution, are
@ -539,23 +563,25 @@ Configuration
Thread Safety Thread Safety
=============
The logging system should support thread-safe operation without The logging system should support thread-safe operation without
any special action needing to be taken by its users. any special action needing to be taken by its users.
Module-Level Functions Module-Level Functions
======================
To support use of the logging mechanism in short scripts and small To support use of the logging mechanism in short scripts and small
applications, module-level functions debug(), info(), warn(), applications, module-level functions ``debug()``, ``info()``, ``warn()``,
error(), critical() and exception() are provided. These work in ``error()``, ``critical()`` and ``exception()`` are provided. These work in
the same way as the correspondingly named methods of Logger - in the same way as the correspondingly named methods of Logger - in
fact they delegate to the corresponding methods on the root fact they delegate to the corresponding methods on the root
logger. A further convenience provided by these functions is that logger. A further convenience provided by these functions is that
if no configuration has been done, basicConfig() is automatically if no configuration has been done, ``basicConfig()`` is automatically
called. called.
At application exit, all handlers can be flushed by calling the function At application exit, all handlers can be flushed by calling the function::
def shutdown(): ... def shutdown(): ...
@ -563,11 +589,13 @@ Module-Level Functions
Implementation Implementation
==============
The reference implementation is Vinay Sajip's logging module [6]. The reference implementation is Vinay Sajip's logging module [6]_.
Packaging Packaging
=========
The reference implementation is implemented as a single module. The reference implementation is implemented as a single module.
This offers the simplest interface - all users have to do is This offers the simplest interface - all users have to do is
@ -576,33 +604,35 @@ Packaging
References References
==========
[1] java.util.logging .. [1] java.util.logging
http://java.sun.com/j2se/1.4/docs/guide/util/logging/ http://java.sun.com/j2se/1.4/docs/guide/util/logging/
[2] log4j: a Java logging package .. [2] log4j: a Java logging package
http://jakarta.apache.org/log4j/docs/index.html http://jakarta.apache.org/log4j/docs/index.html
[3] Protomatter's Syslog .. [3] Protomatter's Syslog
http://protomatter.sourceforge.net/1.1.6/index.html http://protomatter.sourceforge.net/1.1.6/index.html
http://protomatter.sourceforge.net/1.1.6/javadoc/com/protomatter/syslog/syslog-whitepaper.html http://protomatter.sourceforge.net/1.1.6/javadoc/com/protomatter/syslog/syslog-whitepaper.html
[4] MAL mentions his mx.Log logging module: .. [4] MAL mentions his mx.Log logging module:
https://mail.python.org/pipermail/python-dev/2002-February/019767.html https://mail.python.org/pipermail/python-dev/2002-February/019767.html
[5] Jeff Bauer's Mr. Creosote .. [5] Jeff Bauer's Mr. Creosote
http://starship.python.net/crew/jbauer/creosote/ http://starship.python.net/crew/jbauer/creosote/
[6] Vinay Sajip's logging module. .. [6] Vinay Sajip's logging module.
http://www.red-dove.com/python_logging.html http://www.red-dove.com/python_logging.html
Copyright Copyright
=========
This document has been placed in the public domain. This document has been placed in the public domain.
..
Local Variables: Local Variables:
mode: indented-text mode: indented-text
indent-tabs-mode: nil indent-tabs-mode: nil