<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8853457579684330224</id><updated>2012-01-27T11:46:39.800-08:00</updated><title type='text'>Plumber Jack</title><subtitle type='html'>Stuff about Python's logging package.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>29</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-20681055843852654</id><published>2011-12-13T02:27:00.001-08:00</published><updated>2011-12-15T03:36:08.057-08:00</updated><title type='text'>Improved flexibility for log file rotation</title><content type='html'>&lt;p&gt;The &lt;font face="Courier New"&gt;RotatingFileHandler&lt;/font&gt; and &lt;font face="Courier New"&gt;TimedRotatingFileHandler&lt;/font&gt; classes provide basic log file rotation functionality, but there are times when more functionality is required than is provided in these classes. One requirement is to compress log files when they are rotated. Currently, if you want this kind of functionality, you have to subclass the relevant handler and override the &lt;font face="Courier New"&gt;doRollover&lt;/font&gt; method. This works, but requires you to copy and paste the entire method, and then adapt it to your needs; clearly, this is not ideal. An example of this can be found in this ActiveState recipe for a &lt;font color="#000000" face="Courier New"&gt;&lt;a href="http://code.activestate.com/recipes/502265-timedcompressedrotatingfilehandler/"&gt;TimedCompressedRotatingFileHandler&lt;/a&gt;&lt;/font&gt;. This uses the &lt;font face="Courier New"&gt;.zip&lt;/font&gt; format to compress log files.&lt;/p&gt;  &lt;p&gt;In order to provide improved flexibility for log file processing (which will generally be compression, but in practice any transformation could be performed), I propose to add two new methods to these handlers (in their common base class, &lt;font face="Courier New"&gt;BaseRotatingHandler&lt;/font&gt;).&lt;/p&gt;  &lt;p&gt;The first method is named &lt;font face="Courier New"&gt;rotate&lt;/font&gt;. It takes two arguments, &lt;font face="Courier New"&gt;source&lt;/font&gt; and &lt;font face="Courier New"&gt;dest&lt;/font&gt;. The &lt;font face="Courier New"&gt;source&lt;/font&gt; argument is the name of the log file about to be rotated (e.g. &lt;font face="Courier New"&gt;test.log&lt;/font&gt;), and the &lt;font face="Courier New"&gt;dest&lt;/font&gt; argument names the target of the rotation (e.g &lt;font face="Courier New"&gt;test.log.1&lt;/font&gt;). Under normal circumstances, the file named by &lt;font face="Courier New"&gt;dest&lt;/font&gt; should not exist, having been explicitly removed by the rollover logic. the default behaviour is what happens currently – a call to &lt;font face="Courier New"&gt;os.rename(source, dest)&lt;/font&gt;.&lt;/p&gt;  &lt;p&gt;Since we are potentially transforming log files, we might also want to change their extension, so that they are more usable with e.g. file system viewers like Nautilus or Windows Explorer. To facilitate this, a second method is provided, called &lt;font face="Courier New"&gt;filename&lt;/font&gt;. This takes a single argument – the filename of a destination file in a rotation (for example, &lt;font face="Courier New"&gt;test.log.1&lt;/font&gt;)&amp;#160; – and returns a modified version (for example, adding an extension – &lt;font face="Courier New"&gt;test.log.1.gz&lt;/font&gt;). The default behaviour is to return the name unchanged.&lt;/p&gt;  &lt;p&gt;The definition of the new methods is as follows.&lt;/p&gt;  &lt;div class="syntax"&gt;   &lt;pre&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Modify the filename of a log file when rotating.&lt;/span&gt;

&lt;span class="sd"&gt;        This is provided so that a custom filename can be provided,&lt;/span&gt;
&lt;span class="sd"&gt;        say by adding an extension.&lt;/span&gt;

&lt;span class="sd"&gt;        The default implementation returns the name unchanged.&lt;/span&gt;

&lt;span class="sd"&gt;        :param name: The default name for the destination log file.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        When rotating, rotate the current log in the file named by&lt;/span&gt;
&lt;span class="sd"&gt;        source to the file named by dest.&lt;/span&gt;

&lt;span class="sd"&gt;        On entry, the file named by source should exist and the file&lt;/span&gt;
&lt;span class="sd"&gt;        named by dest should not exist.&lt;/span&gt;

&lt;span class="sd"&gt;        On exit, the file named by source should not exist and the&lt;/span&gt;

&lt;span class="sd"&gt;        file named by dest should exist.&lt;/span&gt;

&lt;span class="sd"&gt;        The default implementation simply renames source to dest.&lt;/span&gt;

&lt;span class="sd"&gt;        :param source: The source filename. This is normally the base&lt;/span&gt;
&lt;span class="sd"&gt;                       filename, e.g. 'test.log'&lt;/span&gt;
&lt;span class="sd"&gt;        :param dest:   The destination filename. This is normally&lt;/span&gt;
&lt;span class="sd"&gt;                       what the source is rotated to, e.g. 'test.log.1'.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This will allow a custom rotation strategy to be implemented relatively easily: for example, if you want to gzip rotated log files, you can ensure that &lt;font face="Courier New"&gt;filename&lt;/font&gt; returns &lt;font face="Courier New"&gt;test.log.1.gz&lt;/font&gt; when passed &lt;font face="Courier New"&gt;test.log.1&lt;/font&gt;, and that &lt;font face="Courier New"&gt;rotate&lt;/font&gt; gzips &lt;font face="Courier New"&gt;test.log&lt;/font&gt; to &lt;font face="Courier New"&gt;test.log.1.gz&lt;/font&gt;.&lt;/p&gt;

&lt;p&gt;The ActiveState example I cited earlier used the &lt;font face="Courier New"&gt;.zip&lt;/font&gt; format, but what if you want to use &lt;font face="Courier New"&gt;.gz&lt;/font&gt;, &lt;font face="Courier New"&gt;.bz2&lt;/font&gt;, &lt;font face="Courier New"&gt;.7z&lt;/font&gt; or some other compression algorithm? You would need to support all of them in a single handler, or write multiple handler classes for each format.&lt;/p&gt;

&lt;p&gt;To avoid the need for users to subclass the rotating handlers, I further propose to allow configurability of the &lt;font face="Courier New"&gt;rotate&lt;/font&gt; and &lt;font face="Courier New"&gt;filename&lt;/font&gt; methods by providing two new handler attributes (both, by default, set to &lt;font face="Courier New"&gt;None&lt;/font&gt; in &lt;font face="Courier New"&gt;BaseRotatingHandler&lt;/font&gt;).&lt;/p&gt;

&lt;p&gt;The first handler attribute is named &lt;font face="Courier New"&gt;rotator&lt;/font&gt;. You can set it to a callable that takes two arguments, &lt;font face="Courier New"&gt;source&lt;/font&gt; and &lt;font face="Courier New"&gt;dest&lt;/font&gt;. The meanings of these arguments are the same as for the &lt;font face="Courier New"&gt;rotate&lt;/font&gt; method. If a callable is provided in the &lt;font face="Courier New"&gt;rotator&lt;/font&gt; attribute, it is called by the &lt;font face="Courier New"&gt;rotate&lt;/font&gt; method to perform the rotation.&lt;/p&gt;

&lt;p&gt;The second handler attribute is called &lt;font face="Courier New"&gt;namer&lt;/font&gt;. You can set this to a callable which takes a single argument – the filename of a destination file in a rotation (for example, &lt;font face="Courier New"&gt;test.log.1&lt;/font&gt;)&amp;#160; – and modifies it to, for example, add an extension. If a callable is set, then it is called by the &lt;font face="Courier New"&gt;filename&lt;/font&gt; method to return the new name.&lt;/p&gt;

&lt;p&gt;An example of how you can define a namer and rotator is given in the following snippet, which shows zlib-based compression of the log file.&lt;/p&gt;

&lt;div class="syntax"&gt;
  &lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;namer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.gz&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;rb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;wb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RotatingFileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rotator&lt;/span&gt;
&lt;span class="n"&gt;rh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namer&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I’m aware that these are not “true” &lt;font face="Courier New"&gt;.gz&lt;/font&gt; files, as they are bare compressed data, with no “container” such as you’d find in an actual &lt;font face="Courier New"&gt;gzip&lt;/font&gt; file. This snippet is just for illustration purposes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The &lt;font face="Courier New"&gt;namer&lt;/font&gt; function is called quite a few times during rollover, so it should be as simple and as fast as possible. It should also return the same output every time for a given input, otherwise the rollover behaviour may not work as expected. If either the &lt;font face="Courier New"&gt;namer&lt;/font&gt; or &lt;font face="Courier New"&gt;rotator&lt;/font&gt; function raises an exception, this will be handled in the same way as any other exception during an &lt;font face="Courier New"&gt;emit()&lt;/font&gt; call, i.e. via the &lt;font face="Courier New"&gt;handleError&lt;/font&gt; method of the handler.&lt;/p&gt;

&lt;p&gt;The internal logic for determining which files to delete (when a &lt;font face="Courier New"&gt;backupCount&lt;/font&gt; is specified on a rotating file handler) uses, in the case of&amp;#160; &lt;font face="Courier New"&gt;TimedRotatingFileHandler&lt;/font&gt;, regular expressions for the date/time-based suffix. The regular expressions used have been updated to allow an optional file extension consisting of a period followed by one or more letters and digits.&lt;/p&gt;

&lt;p&gt;These changes will soon appear in Python 3.3’s development repository. Your comments are welcome.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-20681055843852654?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/20681055843852654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/12/improved-flexibility-for-log-file.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/20681055843852654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/20681055843852654'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/12/improved-flexibility-for-log-file.html' title='Improved flexibility for log file rotation'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-542416314547435593</id><published>2011-06-09T14:38:00.001-07:00</published><updated>2011-06-09T14:39:45.430-07:00</updated><title type='text'>Configurability tweaks for SysLogHandler and Formatter</title><content type='html'>&lt;p&gt;There have been some recent changes to SysLogHandler and Formatter which will appear in Python releases.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;font face="Courier New"&gt;SysLogHandler&lt;/font&gt; currently appends a NUL byte to messages it sends to syslog daemons. This is because earlier versions of these daemons expected the NUL terminator to be present, even though it’s not part of the relevant specification (&lt;a href="http://tools.ietf.org/html/rfc5424"&gt;RFC 5424&lt;/a&gt;). More recent releases of these daemons don’t expect the NUL byte to be there, but strip it off if it’s present. And some even more recent daemons follow the specification more strictly, and pass the NUL byte on as part of the message. In order to facilitate dealing with all the various possible destinations for &lt;font face="Courier New"&gt;SysLogHandler&lt;/font&gt; messages, a new class-level attribute, &lt;font face="Courier New"&gt;append_nul&lt;/font&gt;, has been added to &lt;font face="Courier New"&gt;SysLogHandler&lt;/font&gt;. The default value is &lt;font face="Courier New"&gt;True&lt;/font&gt;, thus preserving current behaviour; however, you can set it to &lt;font face="Courier New"&gt;False&lt;/font&gt; for specific handler instances where you don’t want the NUL byte to be appended. This change is expected to appear in Python 3.2.1, which should be released soon. &lt;/li&gt;    &lt;li&gt;In the same vein, the date formatting logic used by &lt;font face="Courier New"&gt;Formatter&lt;/font&gt; has been changed to make it more configurable. Previously, the default ISO 8601 format was hard-coded as in this example: &lt;font face="Courier New"&gt;2010-09-06 22:38:15,292&lt;/font&gt; where the part before the comma is handled by a &lt;font face="Courier New"&gt;strptime&lt;/font&gt; format string (&lt;font face="Courier New"&gt;'%Y-%m-%d %H:%M:%S'&lt;/font&gt;), and the part after the comma is a millisecond value. Because &lt;font face="Courier New"&gt;strptime&lt;/font&gt; does not have a format placeholder for milliseconds, the millisecond value is appended using another format string, &lt;font face="Courier New"&gt;'%s,%03d'&lt;/font&gt; – both of these format strings have been hardcoded into the &lt;font face="Courier New"&gt;Formatter.formatTime&lt;/font&gt; method. That’s set to change with Python 3.3 – these strings will be defined as class-level attributes which can be overridden at the instance level when desired. The names of the attributes are &lt;font face="Courier New"&gt;default_time_format&lt;/font&gt; (for the &lt;font face="Courier New"&gt;strptime&lt;/font&gt; format string) and &lt;font face="Courier New"&gt;default_msec_format&lt;/font&gt; (for appending the millisecond value). &lt;/li&gt; &lt;/ol&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-542416314547435593?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/542416314547435593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/06/configurability-tweaks-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/542416314547435593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/542416314547435593'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/06/configurability-tweaks-for.html' title='Configurability tweaks for SysLogHandler and Formatter'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-505710817904569215</id><published>2011-04-11T04:08:00.001-07:00</published><updated>2011-04-11T04:08:29.479-07:00</updated><title type='text'>Added functionality for basicConfig() in Python 3.3</title><content type='html'>&lt;p&gt;The &lt;font face="Courier New"&gt;logging.basicConfig()&lt;/font&gt; function allows users to easily configure either a console handler or file handler for the root logger, and thereby have them usable by all loggers in their application. While this functionality meets the commonest use cases, there are some slightly less common use cases which can be accommodated without too much work. For example:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Configuring a &lt;font face="Courier New"&gt;FileHandler&lt;/font&gt; with a specific encoding, or with a &lt;font face="Courier New"&gt;delay&lt;/font&gt; parameter which defers opening the file until the first time it’s actually used.&lt;/li&gt;    &lt;li&gt;Configuring a &lt;font face="Courier New"&gt;SysLogHandler&lt;/font&gt;. This could be useful, say, for cron jobs or other admin scripts.&lt;/li&gt;    &lt;li&gt;Configuring multiple handlers with minimal work.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Accordingly, &lt;font face="Courier New"&gt;basicConfig()&lt;/font&gt; in Python 3.3 will have added functionality in the form of a new &lt;font face="courier "&gt;handlers&lt;/font&gt; keyword argument. This defaults to &lt;font face="Courier New"&gt;None&lt;/font&gt;, but when specified it is expected to be an iterable of already-created handler instances, which are to be added to the root logger.&lt;/p&gt;  &lt;p&gt;Any handler in the iterable which does not have a formatter assigned will be assigned the formatter created by &lt;font face="Courier New"&gt;basicConfig()&lt;/font&gt;. This allows users to specify formatters if they need to, but defaults to a common formatter for all handlers which have &lt;em&gt;no&lt;/em&gt; formatter specified.&lt;/p&gt;  &lt;p&gt;This change to &lt;font face="Courier New"&gt;basicConfig()&lt;/font&gt; also includes improved checking for parameter compatibility: it doesn’t make sense to specify &lt;font face="Courier New"&gt;handlers&lt;/font&gt; together with &lt;font face="Courier New"&gt;stream&lt;/font&gt; or &lt;font face="Courier New"&gt;filename&lt;/font&gt; parameters, or to specify &lt;font face="Courier New"&gt;stream&lt;/font&gt; and &lt;font face="Courier New"&gt;filename&lt;/font&gt; parameters together. In Python 3.3, checks have been added so that these invalid combinations will raise a &lt;font face="Courier New"&gt;ValueError&lt;/font&gt;.&lt;/p&gt;  &lt;p&gt;Examples of usage:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;logging.basicConfig(handlers=[logging.FileHandler('test.log', 'w', 'utf-8')])&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;logging.basicConfig(handlers=[logging.SysLogHandler()])&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;logging.basicConfig(handlers=[logging.StreamHandler(), logging.FileHandler('test.log')])&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;I’d welcome feedback on this new functionality, which has already been checked into Python’s Mercurial repository.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-505710817904569215?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/505710817904569215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/04/added-functionality-for-basicconfig-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/505710817904569215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/505710817904569215'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/04/added-functionality-for-basicconfig-in.html' title='Added functionality for basicConfig() in Python 3.3'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-2683441233434730923</id><published>2011-04-11T03:35:00.001-07:00</published><updated>2011-04-11T03:35:02.006-07:00</updated><title type='text'>Logging documentation for Python 2.7 reorganised</title><content type='html'>&lt;p&gt;The logging documentation for Python 2.7 has been reorganised into the same structure as that for Python 3.x. The library reference is supposed to be just that – a reference – so the tutorials have been moved to the HOWTO section. The configuration and handler documentation has also been moved into separate pages. The organisation is now as follows:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://docs.python.org/library/logging"&gt;http://docs.python.org/library/logging&lt;/a&gt; – reference documentation for the &lt;font face="Courier New"&gt;logging&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/library/logging.config"&gt;http://docs.python.org/library/logging.config&lt;/a&gt; - reference documentation for the &lt;font face="Courier New"&gt;logging.config&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/library/logging.handlers"&gt;http://docs.python.org/library/logging.handlers&lt;/a&gt; - reference documentation for the &lt;font face="Courier New"&gt;logging.handlers&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/howto/logging"&gt;http://docs.python.org/howto/logging&lt;/a&gt; – basic and advanced logging tutorials &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/howto/logging-cookbook"&gt;http://docs.python.org/howto/logging-cookbook&lt;/a&gt; – how to use logging in different scenarios &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;There’s a prominent sidebar at the start of the reference documentation pages pointing to the HOWTO sections.&lt;/p&gt;  &lt;p&gt;I hope the results are easier to navigate and to understand. Please feel free to give your feedback.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-2683441233434730923?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/2683441233434730923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/04/logging-documentation-for-python-27.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2683441233434730923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2683441233434730923'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/04/logging-documentation-for-python-27.html' title='Logging documentation for Python 2.7 reorganised'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-1910335082344545499</id><published>2011-02-11T06:22:00.000-08:00</published><updated>2011-02-11T06:37:42.270-08:00</updated><title type='text'>Desktop log viewer released</title><content type='html'>&lt;p&gt;A Qt-based desktop application for viewing logs received across the network has been released. It incorporates some of the feedback gratefully received following an &lt;a href="http://plumberjack.blogspot.com/2011/01/desktop-log-viewer.html"&gt;earlier post&lt;/a&gt; with screencast.&lt;/p&gt;

&lt;p&gt;The viewer has mandatory dependencies on Qt4 and PyQt4, and optional dependencies on ZeroMQ and pyzmq. Because these packages have non-trivial installation procedures (as they use C extensions) and because there may be an old (conflicting) version of Qt installed, the packaging of the application is in binary forms for Windows, Mac OS X (Intel only) and Linux.&lt;/p&gt;

&lt;p&gt;A source distribution is also provided; this assumes that you have PyQt4 and optionally pyzmq already installed.&lt;/p&gt;

&lt;p&gt;Downloads are available from the &lt;a href="http://code.google.com/p/logview/"&gt;project site&lt;/a&gt;, where you can also report issues.&lt;/p&gt;

&lt;p&gt;Screenshot:&lt;/p&gt;

&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://i.imgur.com/5DPno.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="600" width="800" src="http://i.imgur.com/5DPno.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p style="clear:both"&gt;The Linux binary was smoke-tested on: Fedora 14, Ubuntu (Jaunty, Karmic, Maverick), Linux Mint 10 (Ubuntu and Debian variants), Debian Lenny and OpenSUSE 11.3. It looks better on some than on others, but seems to work on all these platforms.&lt;/p&gt;
&lt;p&gt;The Windows binary was smoke-tested on Windows XP and Windows 7.&lt;/p&gt;

&lt;p&gt;The Mac OS X binary was smoke-tested on Leopard (Python 2.5).&lt;/p&gt;

&lt;p&gt;All feedback gratefully received. Please post issues on the &lt;a href="http://code.google.com/p/logview/issues/list"&gt;project site&lt;/a&gt; (requires a Google account), and also feel free to use the &lt;a href="http://groups.google.com/group/logutils/"&gt;mailing list&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-1910335082344545499?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/1910335082344545499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/02/desktop-log-viewer-released.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/1910335082344545499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/1910335082344545499'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/02/desktop-log-viewer-released.html' title='Desktop log viewer released'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-2709265234778538919</id><published>2011-01-17T15:13:00.001-08:00</published><updated>2011-07-16T05:55:31.995-07:00</updated><title type='text'>A desktop log viewer</title><content type='html'>&lt;p&gt;I’ve created a simple desktop application which is a viewer for viewing logging records sent from applications via the SocketHandler and DatagramHandler. It’s early days and the viewer is fairly simple, so I’ve created a screencast so that I can get some feedback on the user interface before spending too much more time on it. If people think it will be useful, I can release it under a 3-clause BSD license.&lt;/p&gt;  &lt;p&gt;I realize that web interfaces are all the rage, but it took me less time to produce this desktop app using PyQt than an equivalent web application would have.&lt;/p&gt;  &lt;p&gt;I developed this on Ubuntu Maverick using the bundled versions of Qt and PyQt, and it seems pretty stable. With older versions of Qt/PyQt (e.g. the versions bundled with Ubuntu Karmic and earlier) I have had segfaults with the identical code - I'm not sure where the problem is.&lt;/p&gt;  &lt;p&gt;The code also works with a recent version of PyQt on Windows 7.&lt;/p&gt;&lt;p&gt;The screencast is around 3 minutes long.&lt;/p&gt;  &lt;div&gt;   &lt;script type="text/javascript" src="http://www.red-dove.com/screencasts/logview/flashobject.js"&gt;&lt;/script&gt;    
   &lt;script type="text/javascript"&gt;
      &lt;!-- To load a movie other then the first one listed in the xml file you can specify a movie=# argument. --&gt;
      &lt;!-- For example, to load the third movie you would do the following: MyProjectName.html?movie=3 --&gt;      
      // &lt;![CDATA[
      var args = new Object();
      var query = location.search.substring(1);
      // Get query string
      var pairs = query.split( "," );
      // Break at comma
      for ( var i = 0; i &lt; pairs.length; i++ )
      {
         var pos = pairs[i].indexOf('=');
         if( pos == -1 )
         {
            continue; // Look for "name=value"
         }
         var argname = pairs[i].substring( 0, pos ); // If not found, skip
         var value = pairs[i].substring( pos + 1 ); // Extract the name
         args[argname] = unescape( value ); // Extract the value
      }
      // ]]&gt;
   &lt;/script&gt;        
      &lt;div id="flashcontent"&gt;      
   &lt;div id="noexpressUpdate"&gt;
     &lt;p&gt;The video content presented here requires JavaScript to be enabled and the  latest version of the Macromedia Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by &lt;a href="http://www.macromedia.com/go/getflashplayer"&gt;downloading here&lt;/a&gt;. &lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
      &lt;script type="text/javascript"&gt;
    // &lt;![CDATA[          
         var fo = new FlashObject( "http://www.red-dove.com/screencasts/logview/logview_controller.swf", "logview_controller.swf", "1024", "767", "8", "#FFFFFF", false, "best" );
         fo.addVariable( "csConfigFile", "http://www.red-dove.com/screencasts/logview/logview_config.xml"  ); 
         fo.addVariable( "csColor"     , "FFFFFF"           );
         
         if( args.movie )
         {
            fo.addVariable( "csFilesetBookmark", args.movie );
         }
         fo.write("flashcontent");        
         // ]]&gt;

    &lt;/script&gt;            
&lt;/div&gt;  &lt;p&gt;I’d be grateful for any feedback about the user interface. I know it’s not especially innovative but then it’s doing a fairly simple job :-)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-2709265234778538919?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/2709265234778538919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2011/01/desktop-log-viewer.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2709265234778538919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2709265234778538919'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2011/01/desktop-log-viewer.html' title='A desktop log viewer'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-98457957352845043</id><published>2010-12-29T03:52:00.001-08:00</published><updated>2010-12-29T09:09:47.522-08:00</updated><title type='text'>Colourising logging output in terminals</title><content type='html'>&lt;p&gt;A nice-to-have feature when logging to terminals is to have the output colourised, typically according to the severity of individual messages. This is fairly straightforward to achieve, but the following points have to be borne in mind:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;On Unix, Linux and OS X, terminals support ANSI escape codes for colourisation, and so a single solution should be able to support all of these platforms. &lt;/li&gt;    &lt;li&gt;Windows does &lt;strong&gt;not&lt;/strong&gt; support ANSI escape codes (though MS-DOS used to, using the ANSI.SYS driver), but does provide an API which can be used to achieve a similar effect. &lt;/li&gt;    &lt;li&gt;When the terminal output is redirected to a file, colourisation should be disabled. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In this post, I’ll present a fairly simple handler which considers each of the above points in its design and implementation. I’ll be adding this handler to the &lt;font face="Courier New"&gt;&lt;a href="http://pypi.python.org/pypi/logutils/"&gt;logutils&lt;/a&gt;&lt;/font&gt; package soon.&lt;/p&gt;  &lt;p&gt;We’ll assume that an entire formatted message will be colourised. If you need to colourise only certain parts of the message, you can adapt the solution presented here fairly easily – perhaps by adding a specialised formatter which is aware of ANSI escape codes.&lt;/p&gt;  &lt;p&gt;We’ll also assume that we only want to be able to set background colour, foreground colour and foreground intensity, and that we’ll only want to set eight colours – red, green, blue, cyan, magenta, yellow, black and white. That’s more or less the lowest common denominator afforded us by ANSI escape sequences. If you have more specialised needs which are supported by your environment, you should be able to adapt the solution presented here, most likely just by subclassing.&lt;/p&gt;  &lt;p&gt;It seems appropriate to base the handler on &lt;font face="Courier New"&gt;logging.StreamHandler&lt;/font&gt;. In addition to importing logging, we’ll need to import &lt;font face="Courier New"&gt;os&lt;/font&gt; (so that we can take appropriate action on Windows) and &lt;font face="Courier New"&gt;ctypes&lt;/font&gt; (so we can access the Windows console API needed to colourise terminal output).&lt;/p&gt;  &lt;p&gt;This is how we start the handler implementation:&lt;/p&gt;  &lt;div class="syntax"&gt;   &lt;pre&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ctypes&lt;/span&gt;&lt;br /&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;br /&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ColorizingStreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="nd"&gt;@property&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_tty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="n"&gt;isatty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'isatty'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;isatty&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;isatty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_tty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_colorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'terminator'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;raise&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Noting that we need to determine when terminal output is piped to a file, we provide a property called &lt;font face="Courier New"&gt;is_tty&lt;/font&gt; on the handler which says whether the handler’s stream is a terminal. If it is, we’ll use a method called &lt;font face="Courier New"&gt;output_colorized&lt;/font&gt; (whose implementation will be platform dependent, and discussed shortly) to do the colourised output; otherwise, we just write the message to the stream.&lt;/p&gt;&lt;p&gt;In order to add colour to the message, we’ll override the &lt;font face="Courier New"&gt;format&lt;/font&gt; method of the handler:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_tty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class="c"&gt;# Don't colorize any traceback&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We first call the base class &lt;font face="Courier New"&gt;format&lt;/font&gt; method to do the actual formatting according to whatever formatter is set for the handler. If we are writing to a terminal, we extract the first line of the formatted text (which could contain an exception traceback), colourise that first line and add the traceback without any colouring.&lt;/p&gt;&lt;p&gt;I did consider putting the colourising logic into a specialised &lt;font face="Courier New"&gt;Formatter&lt;/font&gt; subclass, but decided against it, because:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Colourising is only done when writing to a terminal; a formatter would be unaware of specific output destinations. &lt;/li&gt;&lt;li&gt;Placing the colourising logic in a handler would allow a formatter to be shared with other handlers. &lt;/li&gt;&lt;li&gt;It’s simpler to keep everything in one place. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To do the grunt work, we’ll define the &lt;font face="Courier New"&gt;colorize&lt;/font&gt; helper method together with some data which drives it. We’ll make it the method’s job to add ANSI escape sequence to colour the message, even though Windows does not support ANSI escape sequences natively - we’ll just have to deal with it in the implementation of &lt;font face="Courier New"&gt;output_colorized&lt;/font&gt;, which we’ll come to a little later.&lt;/p&gt;&lt;p&gt;Let’s discuss ANSI escape sequences briefly now – for more information you can consult &lt;a href="http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_Codes"&gt;this article&lt;/a&gt;. Basically, colourising is done by placing a special character sequence in the character stream sent to the terminal. The sequence always starts with an ESC (ASCII 27) followed by a left bracket character ([). The sequence always ends with an ‘m’. In between is one or more integer parameters (as printed characters in decimal format, separated by semicolons). These parameters have the following meanings:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;0 – reset background and foreground colours and intensity to the defaults. &lt;/li&gt;&lt;li&gt;1 – set the foreground intensity on. &lt;/li&gt;&lt;li&gt;between 30 and 37 – set the foreground colour to one of the values in &lt;a href="http://en.wikipedia.org/wiki/ANSI_escape_code#Colors"&gt;this table&lt;/a&gt;, after subtracting 30 from the parameter. &lt;/li&gt;&lt;li&gt;between 40 and 47 - set the background colour to one of the values in &lt;a href="http://en.wikipedia.org/wiki/ANSI_escape_code#Colors"&gt;this table&lt;/a&gt;, after subtracting 40 from the parameter. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There are other parameters with other meanings in the ANSI specification, but we won’t concern ourselves with those as they aren’t relevant to our simple colourisation scheme.&lt;/p&gt;&lt;p&gt;So, we use a simple &lt;font face="Courier New"&gt;color_map&lt;/font&gt; dictionary that allows mapping colour names to integer colour values. We also use a &lt;font face="Courier New"&gt;level_map&lt;/font&gt; dictionary which maps logging levels to a tuple encoding the background colour, foreground colour (colour names) and intensity (a Boolean). If no background colour is specified (&lt;font face="Courier New"&gt;None&lt;/font&gt;) , the default is used. Because Windows terminals usually have a dark background and Linux terminals a light background, the &lt;font face="Courier New"&gt;level_map&lt;/font&gt; is defined slightly differently on Windows. We also define the escape sequence beginning (called the CSI for Control Sequence Introducer/Initiator), and the reset sequence.&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;    &lt;span class="c"&gt;# color names to indices&lt;/span&gt;&lt;br /&gt;    &lt;span class="n"&gt;color_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'yellow'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'magenta'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'cyan'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class="s"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class="p"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="c"&gt;#levels to (background, foreground, bold/intense)&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'nt'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class="n"&gt;level_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'yellow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRITICAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class="n"&gt;level_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'yellow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRITICAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class="n"&gt;csi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\x1b&lt;/span&gt;&lt;span class="s"&gt;['&lt;/span&gt;&lt;br /&gt;    &lt;span class="n"&gt;reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\x1b&lt;/span&gt;&lt;span class="s"&gt;[0m'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;colorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;40&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;';'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br /&gt;                                   &lt;span class="s"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the mappings and constants have been defined, the &lt;font face="Courier New"&gt;colorize&lt;/font&gt; method is very simple. If the record’s level is in the level map, we get the tuple for background and foreground colours and intensity, get the appropriate commands and construct the escape sequence. We return the passed in message, bracketed by the initial escape sequence which encodes the appropriate colours and intensity for the record’s level, and the final escape sequence which resets the terminal to default colours and intensity.&lt;/p&gt;&lt;p&gt;The implementation thus far adds ANSI escape sequences to messages to colourise them. These are natively supported on Unix, Linux and OS X, but not so on Windows. We therefore define the &lt;font face="Courier New"&gt;output_colorized&lt;/font&gt; message, which is called from &lt;font face="Courier New"&gt;emit&lt;/font&gt;,&amp;#160; differently on NT and non-NT platforms. The non-NT implementation is very simple: assuming ANSI support is native, we just write the colourised message to the output stream. For Windows, we need to interpret the ANSI escape sequences and implement them with Win32 API calls. Here are the implementations:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'nt'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;output_colorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;&lt;br /&gt;        &lt;span class="n"&gt;ansi_esc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;r'\x1b\[((\d+)(;(\d+))*)m'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class="n"&gt;nt_color_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# black&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# red&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# green&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# yellow&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# blue&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# magenta&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# cyan&lt;/span&gt;&lt;br /&gt;            &lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c"&gt;# white&lt;/span&gt;&lt;br /&gt;        &lt;span class="p"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;output_colorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ansi_esc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;br /&gt;            &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'fileno'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c"&gt;# stdout or stderr&lt;/span&gt;&lt;br /&gt;                    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel32&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStdHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                    &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;br /&gt;                    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;br /&gt;                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;br /&gt;                        &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;br /&gt;                        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mf"&gt;40&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;47&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                                &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nt_color_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;br /&gt;                            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="mf"&gt;30&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;37&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                                &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nt_color_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;br /&gt;                            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                                &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x08&lt;/span&gt; &lt;span class="c"&gt;# foreground intensity on&lt;/span&gt;&lt;br /&gt;                            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c"&gt;# reset to default color&lt;/span&gt;&lt;br /&gt;                                &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x07&lt;/span&gt;&lt;br /&gt;                            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br /&gt;                                &lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="c"&gt;# error condition ignored&lt;/span&gt;&lt;br /&gt;                        &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel32&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetConsoleTextAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because the integer values for ANSI colour codes differ slightly from those for Windows, we use an &lt;font face="Courier New"&gt;nt_color_map&lt;/font&gt; dictionary to map between the two. We use regular expressions to split the colourised message into escape sequences and literal text to be output. (Note that because there are four groups in the regular expression, the list will contain four entries for each ANSI sequence found.)&lt;/p&gt;&lt;p&gt;In order to colourise the text in the terminal, we need to obtain a Windows console handle for the output stream, using the &lt;font color="#000000" face="Courier New"&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms683231(v=vs.85).aspx"&gt;GetStdHandle&lt;/a&gt;&lt;/font&gt; Win32 API. If this handle is not obtainable, literal text is output but the ANSI escape sequences are ignored. If the handle &lt;em&gt;is&lt;/em&gt; obtained, then the ANSI sequences are converted to the corresponding Win32 attribute values and the &lt;font face="Courier New"&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms686047(VS.85).aspx"&gt;SetConsoleTextAttribute&lt;/a&gt;&lt;/font&gt; API is used to write them to the console.&lt;/p&gt;&lt;p&gt;The whole thing is available a ready-to-run form in &lt;a href="https://gist.github.com/758430"&gt;this gist&lt;/a&gt;, which should run on all Pythons &amp;gt;= 2.5 (and Python 2.4, too, if you have &lt;font face="Courier New"&gt;ctypes&lt;/font&gt; installed). Here are some screenshots of running the scripts on Ubuntu, Windows 7, Windows 2008, and Mac OS X, showing the expected results for both terminal output and output piped to a file.&lt;/p&gt;&lt;p&gt;Ubuntu Jaunty:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_-N8xZeqqzq4/TRsg5HptZ9I/AAAAAAAABJU/LAgfICxhWNA/s1600-h/ansistrm-jaunty%5B6%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="ansistrm-jaunty" border="0" alt="ansistrm-jaunty" src="http://lh6.ggpht.com/_-N8xZeqqzq4/TRsg56gX08I/AAAAAAAABJY/6IB-vSyTWp8/ansistrm-jaunty_thumb%5B4%5D.png?imgmax=800" width="671" height="468" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Windows 7:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_-N8xZeqqzq4/TRsg606DDZI/AAAAAAAABJc/Hf8FSZhvqXU/s1600-h/ansistrm-win7%5B3%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="ansistrm-win7" border="0" alt="ansistrm-win7" src="http://lh5.ggpht.com/_-N8xZeqqzq4/TRsg7eL0IkI/AAAAAAAABJg/NH6_etJ2gio/ansistrm-win7_thumb%5B1%5D.png?imgmax=800" width="681" height="446" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Windows 2008 (Python 2.4):&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_-N8xZeqqzq4/TRsg7wZ3BOI/AAAAAAAABJk/8sqIPgJ4HoY/s1600-h/ansistrm-win2k8%5B7%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="ansistrm-win2k8" border="0" alt="ansistrm-win2k8" src="http://lh5.ggpht.com/_-N8xZeqqzq4/TRsg8YeMoyI/AAAAAAAABJo/L-dALY7fVkQ/ansistrm-win2k8_thumb%5B3%5D.png?imgmax=800" width="675" height="336" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Mac OS X:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_-N8xZeqqzq4/TRsy0EnOEeI/AAAAAAAABJs/A43K_azKT-A/s1600-h/ansistrm-macosx%5B3%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="ansistrm-macosx" border="0" alt="ansistrm-macosx" src="http://lh5.ggpht.com/_-N8xZeqqzq4/TRsy05x2UEI/AAAAAAAABJw/gHtSMopup7Q/ansistrm-macosx_thumb%5B1%5D.png?imgmax=800" width="589" height="451" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;If you want different colour settings, you can probably achieve this just by changing the &lt;font face="Courier New"&gt;level_map&lt;/font&gt; contents. If you want to insert colourisation for just part of your messages, you could change the logic in the &lt;font face="Courier New"&gt;colorize&lt;/font&gt; and/or &lt;font face="Courier New"&gt;format&lt;/font&gt; methods.&amp;#160; Of course, you can also override &lt;font face="Courier New"&gt;output_colorized&lt;/font&gt; and &lt;font face="Courier New"&gt;colorize&lt;/font&gt; if you need to support additional ANSI escape sequences.&lt;/p&gt;&lt;p&gt;Your comments are welcome, particularly any suggestions for improvements. Thanks for reading.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-98457957352845043?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/98457957352845043/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/98457957352845043'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/98457957352845043'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html' title='Colourising logging output in terminals'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_-N8xZeqqzq4/TRsg56gX08I/AAAAAAAABJY/6IB-vSyTWp8/s72-c/ansistrm-jaunty_thumb%5B4%5D.png?imgmax=800' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-6762392748932642458</id><published>2010-12-19T07:18:00.001-08:00</published><updated>2010-12-19T07:18:35.945-08:00</updated><title type='text'>Logging documentation for Python 3.2 reorganised</title><content type='html'>&lt;p&gt;After comments that the length of logging documentation was making it hard to navigate, and after the addition of a new basic tutorial, the documentation has been reorganised. The library reference is supposed to be just that – a reference – so the tutorials have been moved to the HOWTO section. The configuration and handler documentation has also been moved into separate pages. The organisation of the Python 3.2 logging documentation is now as follows:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://docs.python.org/dev/library/logging"&gt;http://docs.python.org/dev/library/logging&lt;/a&gt; – reference documentation for the &lt;font face="Courier New"&gt;logging&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/dev/library/logging.config"&gt;http://docs.python.org/dev/library/logging.config&lt;/a&gt; - reference documentation for the &lt;font face="Courier New"&gt;logging.config&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/dev/library/logging.handlers"&gt;http://docs.python.org/dev/library/logging.handlers&lt;/a&gt; - reference documentation for the &lt;font face="Courier New"&gt;logging.handlers&lt;/font&gt; module &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/dev/howto/logging"&gt;http://docs.python.org/dev/howto/logging&lt;/a&gt; – basic and advanced logging tutorials &lt;/li&gt;    &lt;li&gt;&lt;a href="http://docs.python.org/dev/howto/logging-cookbook"&gt;http://docs.python.org/dev/howto/logging-cookbook&lt;/a&gt; – how to use logging in different scenarios &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;There’s a prominent sidebar at the start of the reference documentation pages pointing to the HOWTO sections.&lt;/p&gt;  &lt;p&gt;I hope the results are easier to navigate and to understand. Please feel free to give your feedback.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-6762392748932642458?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/6762392748932642458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/12/logging-documentation-for-python-32.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6762392748932642458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6762392748932642458'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/12/logging-documentation-for-python-32.html' title='Logging documentation for Python 3.2 reorganised'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-450932978527394317</id><published>2010-12-09T15:58:00.001-08:00</published><updated>2010-12-10T14:59:25.915-08:00</updated><title type='text'>Proposed changes to logging defaults</title><content type='html'>&lt;p&gt;Following &lt;a href="http://code.activestate.com/lists/python-dev/102572/"&gt;recent discussion on python-dev&lt;/a&gt;, some changes to logging’s default behaviour are likely to be implemented soon, perhaps as soon as in 3.2b2 (3.2b1 was released just a few days ago). By &lt;em&gt;default behaviour&lt;/em&gt; is meant behaviour in the absence of any explicit logging configuration.&lt;/p&gt;  &lt;p&gt;The current behaviour, which dates from logging’s first appearance in Python, is as follows. When a logging event needs to be output, the logging package looks for handlers which have been configured by the application. If no such handlers are found, the package prints a message “No handlers could be found for logger XXX”, naming the logger that was used to log the event. (The message is only ever printed once, even if subsequent logging calls are made. Furthermore, the message is only printed when &lt;font face="Courier New"&gt;logging.raiseExceptions&lt;/font&gt; is &lt;font face="Courier New"&gt;True&lt;/font&gt;, which is the “development” mode of operation for logging. When &lt;font face="Courier New"&gt;logging.raiseExceptions&lt;/font&gt; is &lt;font face="Courier New"&gt;False&lt;/font&gt;, the “production” mode of operation for logging, the message isn’t printed, even once.)&lt;/p&gt;  &lt;p&gt;This creates some problems when an application (or simple script, or interactive session – I’ll use &lt;em&gt;script&lt;/em&gt; to mean any of these) meets the following criteria:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;It uses a library which does logging under the covers. &lt;/li&gt;    &lt;li&gt;It doesn’t configure any handlers. &lt;/li&gt;    &lt;li&gt;The library code invoked logs events of severity WARNING or above. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Because the default verbosity for logging is WARNING, the logging package looks for handlers, and, not finding any, prints the one-off “No handlers could be found …” message. This may be a surprise to the author of the script and also for its users; it can generate support calls to the author from the users. The message was provided as a means of warning developers, who would have been new to logging, about a possible misconfiguration. (Of course, when logging was introduced,&amp;#160; &lt;em&gt;everyone&lt;/em&gt; was new to it.) The importance of the message is less now than it was before, because many people are familiar with how logging works and provide good support on &lt;font face="Courier New"&gt;comp.lang.python&lt;/font&gt; to people who are newcomers to logging, or who run into issues that are simple to resolve.&lt;/p&gt;  &lt;p&gt;To deal with the problem described above, the approach suggested has been for library developers to add a &lt;font face="Courier New"&gt;NullHandler&lt;/font&gt; instance to their top-level logger. This prevents messages from library code from causing the misconfiguration message to be printed, because a handler — the &lt;font face="Courier New"&gt;NullHandler&lt;/font&gt; — is always found when handling events from the library. If a script developer adds logging events in their own code which need to be handled, then the misconfiguration message will still appear, and that’s as it should be.&lt;/p&gt;  &lt;p&gt;When the logging package was introduced, this was done with a philosophy that logging is an &lt;em&gt;adjunct&lt;/em&gt; to normal application activity — meaning that a script should work in exactly the same way with or without the presence of logging. So logging tries to work as unobtrusively as possible, and is set up so that in production use, exceptions that occur in logging (e.g. mismatched format string and parameters) will not be raised, allowing a script to carry on uninterrupted. Also pertinent is the Unix philosophy that verbosity should be kept to a minimum.&lt;/p&gt;  &lt;p&gt;The above describes the current state of play, but things are now set to change. The rationale for changing is:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The present behaviour goes against the Zen of Python, which says “Errors should never pass silently. Unless explicitly silenced.” &lt;/li&gt;    &lt;li&gt;Python core developers want to be able to use the logging package not just as an adjunct to doing real work in the standard library, but to actually &lt;em&gt;do&lt;/em&gt; the real work when it comes to the output of warning and error messages. &lt;/li&gt;    &lt;li&gt;In some cases exceptions can’t be usefully raised, and writing out a message is the only way to provide information about an error condition which has been detected. Providing it via logging provides more flexibility in dealing with it than would a bare write to &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt;. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;These changes will, in due course, bring benefits such as being able to redirect standard library warning and error messages, which are currently written to &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt;, to some destination of a developer’s choosing. However, it may cause some inconvenience to existing users, because the proposed behaviour change is not backwards-compatible.&lt;/p&gt;  &lt;p&gt;How will the change be implemented? This is still being thought about, but the current front runner is to implement through the idea of a “handler of last resort”. When no handler is found when processing an event which needs to be output, instead of printing the misconfiguration message, the system will use the “last resort” handler to handle the event. The “last resort” handler will be (by default) a &lt;font face="Courier New"&gt;StreamHandler&lt;/font&gt; using &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt; as its stream, with its level set to &lt;font face="Courier New"&gt;WARNING&lt;/font&gt;. Details about how to preserve existing behaviour are still being worked on; one thought is that you can get existing behaviour by setting the last resort handler to &lt;font face="Courier New"&gt;None&lt;/font&gt;.&lt;/p&gt;  &lt;p&gt;What’s the impact on users of logging?&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;If you’re using a version of Python earlier than 3.2, there’s no impact. Things will stay as they are. &lt;/li&gt;    &lt;li&gt;Script developers: If you configure logging (i.e. add handlers), there should be no impact – the behaviour you see shouldn’t change, as long as there is no situation where a handler isn’t found. If you don’t configure logging, then in place of a misconfiguration message, you’ll see events of severity &lt;font face="Courier New"&gt;WARNING&lt;/font&gt; and above printed to &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt; without any specific formatting (i.e. as if the format string were &lt;font face="Courier New"&gt;&amp;quot;%(message)s&amp;quot;&lt;/font&gt;). &lt;/li&gt;    &lt;li&gt;Library developers: If the script using your library configured logging, you should see no change in behaviour. Otherwise: if you added a &lt;font face="Courier New"&gt;NullHandler&lt;/font&gt; to your library’s top-level logger, you should see no change in behaviour, but remember — your warning and error messages will not be shown to end-users. If you reverse the &lt;font face="Courier New"&gt;NullHandler&lt;/font&gt; addition or didn’t add it in the first place, then instead of a misconfiguration message, end-users will see your warning and error messages on &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt;. &lt;/li&gt;    &lt;li&gt;End users: If the script being run configured logging, you should see no change in behaviour. If it didn’t, then in place of a misconfiguration message, you’ll see events of severity &lt;font face="Courier New"&gt;WARNING&lt;/font&gt; and above printed to &lt;font face="Courier New"&gt;sys.stderr&lt;/font&gt; without any specific formatting. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;I hope I’ve covered everything, but should any kind reader spot some error or omission, please let me know.&lt;/p&gt;  &lt;p&gt;Please feel free to comment giving your views on the proposed change, whether you approve of it or whether you think it affects you adversely. Thanks for reading.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-450932978527394317?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/450932978527394317/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/12/proposed-changes-to-logging-defaults.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/450932978527394317'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/450932978527394317'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/12/proposed-changes-to-logging-defaults.html' title='Proposed changes to logging defaults'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-5265420511400257119</id><published>2010-12-03T05:02:00.001-08:00</published><updated>2010-12-03T05:11:52.920-08:00</updated><title type='text'>Getting more control over LogRecord creation</title><content type='html'>&lt;p&gt;Every logging event is represented by a &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; instance. When an event is logged and not filtered out by a logger’s level, a &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; is created, populated with information about the event and then passed to the handlers for that logger (and its ancestors, up to and including the logger where further propagation up the hierarchy is disabled). Before Python 3.2, there were only two places where this creation was done:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;font face="Courier New"&gt;Logger.makeRecord&lt;/font&gt;, which is called in the normal process of logging an event. This invoked &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; directly to create an instance. &lt;/li&gt;    &lt;li&gt;&lt;font face="Courier New"&gt;logging.makeLogRecord&lt;/font&gt;, which is called with a dictionary containing attributes to be added to the &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt;. This is typically invoked when a suitable dictionary has been received over the network (e.g. in pickle form via a &lt;font face="Courier New"&gt;SocketHandler&lt;/font&gt;, or in JSON form via an &lt;font face="Courier New"&gt;HTTPHandler&lt;/font&gt;). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;This has usually meant that if you need to do anything special with a &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt;, you’ve had to do one of the following.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Create your own &lt;font face="Courier New"&gt;Logger&lt;/font&gt; subclass, which overrides &lt;font face="Courier New"&gt;makeRecord()&lt;/font&gt;, and set it using &lt;font face="Courier New"&gt;logging.setLoggerClass&lt;/font&gt; before any loggers that you care about are instantiated. &lt;/li&gt;    &lt;li&gt;Add a &lt;font face="Courier New"&gt;Filter&lt;/font&gt; to a logger or handler, which does the necessary special manipulation you need when its &lt;font face="Courier New"&gt;filter()&lt;/font&gt; method is called. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The first approach would be a little unwieldy in the scenario where (say) several different libraries wanted to do different things. Each would attempt to set its own &lt;font face="Courier New"&gt;Logger&lt;/font&gt; subclass, and the one which did this last would win. The second approach works reasonably well for many cases, but does not allow you to e.g. use a specialized subclass of &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt;. Library developers can set a suitable filter on their loggers, but they would have to remember to do this every time they introduced a new logger (which they would do simply by adding new packages or modules and doing &lt;font face="Courier New"&gt;logger = logging.getLogger(__name__)&lt;/font&gt; at module level). It’s probably one too many things to think about. They could also add the filter to a &lt;font face="Courier New"&gt;NullHandler&lt;/font&gt; attached to their top-level logger, but this would not be invoked if an application developer attached a handler to a lower-level library logger – so output from that handler would not reflect the intentions of the library developer.&lt;/p&gt;  &lt;p&gt;In Python 3.2,&lt;font face="Cambria"&gt; &lt;/font&gt;&lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; creation will be done through a factory, which you can specify. The factory is just a callable you can set with &lt;font face="Courier New"&gt;logging.setLogRecordFactory(factory)&lt;/font&gt;, and interrogate with &lt;font face="Courier New"&gt;logging.getLogRecordFactory()&lt;/font&gt;. The factory is invoked with the same signature as the &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; constructor, as &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; is the default setting for the factory:&lt;/p&gt;  &lt;div class="syntax"&gt;   &lt;pre&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lineno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The parameters are as follows:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;font face="Courier New"&gt;name&lt;/font&gt; – the name of the logger used. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;lvl&lt;/font&gt; – the numeric value of the event’s level. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;pathname&lt;/font&gt;, &lt;font face="Courier New"&gt;lineno&lt;/font&gt; – the location in the source code where the logging call appears. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;msg&lt;/font&gt;, &lt;font face="Courier New"&gt;args&lt;/font&gt; – the logging message and arguments. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;exc_info&lt;/font&gt; – any exception info, or None. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;func&lt;/font&gt; – the name of the function or method from which the logging call was made. &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;sinfo&lt;/font&gt; – a traceback such as is produced by traceback.print_stack. (This is not the same as the exception traceback.) &lt;/li&gt;&lt;li&gt;&lt;font face="Courier New"&gt;kwargs&lt;/font&gt; – currently empty, but this could change in future versions of logging. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This approach allows a custom factory to control all aspects of &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; creation. For example, you could return a subclass, or just add some additional attributes to the record once created, using a pattern similar to this:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="n"&gt;old_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogRecordFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom_attribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xdecafbad&lt;/span&gt; &lt;br /&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;br /&gt; &lt;br /&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLogRecordFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record_factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This pattern allows different libraries to chain factories together, and as long as they don’t overwrite each other’s attributes or unintentionally overwrite the attributes provided as standard, there should be no surprises. However, it should be borne in mind that each link in the chain adds run-time overhead to all logging operations, and the technique should only be used when the use of a &lt;font face="Courier New"&gt;Filter&lt;/font&gt; does not provide the desired result.&lt;p&gt;These changes have been added to Python 3.2 (i.e. in the &lt;font face="Courier New"&gt;py3k&lt;/font&gt; branch) and should be in the first beta. Feel free to try them out. Your feedback is, as always, most welcome.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-5265420511400257119?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/5265420511400257119/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/12/getting-more-control-over-logrecord.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5265420511400257119'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5265420511400257119'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/12/getting-more-control-over-logrecord.html' title='Getting more control over LogRecord creation'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-117285494098821244</id><published>2010-10-31T09:28:00.001-07:00</published><updated>2010-10-31T09:28:21.620-07:00</updated><title type='text'>logutils: Using recent logging features with older Python versions</title><content type='html'>&lt;p&gt;Python’s development is a process of continuous improvement, and the standard library of course undergoes changes as new versions of Python are released. In the logging package, there have been numerous improvements in recent months:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Dictionary-based configuration (&lt;a href="http://www.python.org/dev/peps/pep-0391/"&gt;PEP 391&lt;/a&gt;) &lt;/li&gt;    &lt;li&gt;&lt;a href="http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html"&gt;QueueHandler&lt;/a&gt; for working with multiprocessing and in-process queues &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In addition, there have been a few bits of utility code which have been posted here:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Classes which facilitate use of logging in particular scenarios, such as &lt;a href="http://plumberjack.blogspot.com/2010/09/unit-testing-and-logging.html"&gt;unit testing&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Classes which facilitate building logging messages using &lt;a href="http://plumberjack.blogspot.com/2010/10/supporting-alternative-formatting.html"&gt;str.format or string.Template&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Many of the enhancements have been added as new features which will emerge in Python 3.2, but cannot be added to Python 2.X because there are no Python 2.X branches where new features can be added. Python 2.7, the last 2.X release, is in maintenance mode and will only accept bug-fixes which maintain backwards compatibility.&lt;/p&gt;  &lt;p&gt;Although Python 3.2+ is the way forward for Python development, many users will be constrained to use 2.X for some considerable time yet, mostly because libraries they depend on have not yet been ported to 3.X, but sometimes also because their codebase is too large to consider porting to 3.X, when viewed from a cost/benefit viewpoint, or perhaps because their code needs to remain workable under 2.X and they cannot maintain two separate codebases.&lt;/p&gt;  &lt;p&gt;For those users who are forced to stick with Python 2.X but who want to use some of the features added recently to logging, the &lt;font face="Courier New"&gt;logutils&lt;/font&gt; project offers one way in which they can access these features. This is available via PyPI so it can be installed using &lt;font face="Courier New"&gt;pip&lt;/font&gt; or &lt;font face="Courier New"&gt;easy_install&lt;/font&gt;. The package is currently at release 0.2 and contains the following:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Dictionary-based configuration (PEP 391) which is broadly compatible with Python 2.7 and 3.2.&lt;/li&gt;    &lt;li&gt;The &lt;font face="Courier New"&gt;LoggerAdapter&lt;/font&gt; class is easier to subclass. &lt;/li&gt;    &lt;li&gt;The &lt;font face="Courier New"&gt;QueueHandler&lt;/font&gt; and &lt;font face="Courier New"&gt;QueueListener&lt;/font&gt; classes are available for use with in-process, multiprocessing and other types of queue, as described in &lt;a href="http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html"&gt;this post&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;The &lt;font face="Courier New"&gt;TestHandler&lt;/font&gt; and &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; classes are available for use in unit tests, as described in &lt;a href="http://plumberjack.blogspot.com/2010/09/unit-testing-and-logging.html"&gt;this post&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;An enhanced &lt;font face="Courier New"&gt;HTTPHandler&lt;/font&gt; allows the use of secure connections and credentials for logging in to websites. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The package needs Python 2.4 or later. Some features are not available in Python 2.4, due to limitations of that Python version. For example, &lt;font face="Courier New"&gt;str.format&lt;/font&gt; is only available from Python 2.6 onwards;&amp;#160; &lt;font face="Courier New"&gt;LoggerAdapter&lt;/font&gt; relies on a feature added in Python 2.5, namely the &lt;font face="Courier New"&gt;extra&lt;/font&gt; parameter for adding additional context to logs; and named handlers are not available for Python 2.X versions &amp;lt; 2.7 and Python 3.X versions &amp;lt; 3.2. (As a consequence of this, incremental handler configuration is not available on the versions where named handlers are not available.) &lt;/p&gt;  &lt;p&gt;Project downloads are &lt;a href="http://code.google.com/p/logutils/downloads/list"&gt;here&lt;/a&gt;. You can post issues &lt;a href="http://code.google.com/p/logutils/issues/entry"&gt;here&lt;/a&gt; (needs a Google account). Documentation is available &lt;a href="http://packages.python.org/logutils/"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-117285494098821244?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/117285494098821244/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/10/logutils-using-recent-logging-features.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/117285494098821244'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/117285494098821244'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/10/logutils-using-recent-logging-features.html' title='logutils: Using recent logging features with older Python versions'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-4114591115987761563</id><published>2010-10-28T08:09:00.001-07:00</published><updated>2010-11-01T00:15:04.972-07:00</updated><title type='text'>Supporting alternative formatting styles in logging</title><content type='html'>&lt;p&gt;When logging was added to the Python standard library, the only way of formatting messages with variable content was to use the &lt;a href="http://docs.python.org/library/stdtypes.html#string-formatting-operations"&gt;%-formatting&lt;/a&gt; method. Since then, Python has gained two new formatting approaches: &lt;font face="Courier New"&gt;&lt;a href="http://docs.python.org/dev/library/string.html#string.Template.substitute "&gt;string.Template&lt;/a&gt;&lt;/font&gt; (added in Python 2.4) and &lt;font face="Courier New"&gt;&lt;a href="http://docs.python.org/dev/library/stdtypes.html#str.format"&gt;str.format&lt;/a&gt;&lt;/font&gt; (added in Python 2.6).&lt;/p&gt;  &lt;p&gt;Now, Python’s logging package will provide improved support for these two additional formatting styles. The &lt;font face="Courier New"&gt;Formatter&lt;/font&gt; class in logging has been enhanced for Python 3.2 to take an additional, optional keyword parameter named &lt;font face="Courier New"&gt;style&lt;/font&gt;. This defaults to &lt;font face="Courier New"&gt;'%'&lt;/font&gt;, but other possible values are &lt;font face="Courier New"&gt;'{'&lt;/font&gt; and &lt;font face="Courier New"&gt;'$'&lt;/font&gt;, which correspond to the other two formatting styles. Backwards compatibility is maintained by default (as you would expect), but by explicitly specifying a &lt;font face="Courier New"&gt;style&lt;/font&gt; parameter , you get the ability to specify format strings which work with &lt;font face="Courier New"&gt;str.format&lt;/font&gt; or &lt;font face="Courier New"&gt;string.Template&lt;/font&gt;. Here’s an example console session to show the possibilities:&lt;/p&gt;  &lt;div class="syntax"&gt;   &lt;pre&gt;&lt;span class="go"&gt;vinay@eta-jaunty:~$ python3.2&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;Python 3.2a3+ (py3k:85857, Oct 27 2010, 18:25:50) &lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;[GCC 4.3.3] on linux2&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;Type &amp;quot;help&amp;quot;, &amp;quot;copyright&amp;quot;, &amp;quot;credits&amp;quot; or &amp;quot;license&amp;quot; for more information.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'{asctime} {name} {levelname:8s} {message}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'{'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'foo.bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'This is a DEBUG message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;2010-10-28 15:11:55,341 foo.bar DEBUG    This is a DEBUG message&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'This is a CRITICAL message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;2010-10-28 15:12:11,526 foo.bar CRITICAL This is a CRITICAL message&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'$asctime $name ${levelname} $message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'$'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'This is a DEBUG message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;2010-10-28 15:13:06,924 foo.bar DEBUG This is a DEBUG message&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'This is a CRITICAL message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;2010-10-28 15:13:11,494 foo.bar CRITICAL This is a CRITICAL message&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that the formatting of logging messages for final output to logs is completely independent of how an individual logging message is constructed. That can still use %-formatting, as shown here:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'This is an&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'other,'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'ERROR,'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;2010-10-28 15:19:29,833 foo.bar ERROR This is another, ERROR, message&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Logging calls (&lt;font face="Courier New"&gt;logger.debug()&lt;/font&gt;, &lt;font face="Courier New"&gt;logger.info()&lt;/font&gt; etc.) only take positional parameters for the actual logging message itself, with keyword parameters used only for determining options for how to handle the actual logging call (e.g. the &lt;font face="Courier New"&gt;exc_info&lt;/font&gt; keyword parameter to indicate that traceback information should be logged, or the &lt;font face="Courier New"&gt;extra&lt;/font&gt; keyword parameter to indicate additional contextual information to be added to the log). So you cannot directly make logging calls using &lt;font face="Courier New"&gt;str.format&lt;/font&gt; or &lt;font face="Courier New"&gt;string.Template&lt;/font&gt; syntax, because internally the logging package uses %-formatting to merge the format string and the variable arguments. There would no changing this while preserving backward compatibility, since all logging calls which are out there in existing code will be using %-format strings.&lt;/p&gt;&lt;p&gt;There is, however, a way that you can use {}- and $- formatting to construct your individual log messages. Recall that for a message you can use an &lt;a href="http://docs.python.org/library/logging.html#using-arbitrary-objects-as-messages"&gt;arbitrary object&lt;/a&gt; as a message format string, and that the logging package will call &lt;font face="Courier New"&gt;str()&lt;/font&gt; on that object to get the actual format string. Consider the following two classes:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BraceMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;br /&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;br /&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DollarMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;br /&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br /&gt;        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;string&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;br /&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substitute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Either of these can be used in place of a format string, to allow {}- or $-formatting to be used to build the actual “message” part which appears in the formatted log output in place of “%(message)s” or “{message}” or “$message”. It’s a little unwieldy to use the class names whenever you want to log something, but it’s quite palatable if you use an alias such as __ (double underscore – not to be confused with _, the single underscore used as a synonym/alias for &lt;font face="Courier New"&gt;&lt;a href="http://docs.python.org/library/gettext.html#gettext.gettext"&gt;gettext.gettext&lt;/a&gt;&lt;/font&gt; or its brethren).&lt;/p&gt;&lt;p&gt;The above classes, and recent additions to the logging package, will be published in a package called &lt;font face="Courier New"&gt;logutils&lt;/font&gt;, intended for use with Python 2.4 or later. This is already &lt;a href="http://pypi.python.org/pypi/logutils/"&gt;available for download via PyPI&lt;/a&gt; (and will be the subject of a future blog post). They can be used as follows:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logutils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BraceMessage&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Message with {0} {1}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'placeholders'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;Message with 2 placeholders&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Message with coordinates: ({point.x:.2f}, {point.y:.2f})'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;Message with coordinates: (0.50, 0.50)&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logutils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DollarMessage&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Message with $num $what'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'placeholders'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br /&gt;&lt;span class="go"&gt;Message with 2 placeholders&lt;/span&gt;&lt;br /&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One thing to note is that you pay no significant performance penalty with this approach: the actual formatting happens not when you make the logging call, but when (and if) the logged message is actually about to be output to a log by a handler. So the only slightly unusual thing which might trip you up is that the parentheses go around the format string &lt;em&gt;and&lt;/em&gt; the arguments, not just the format string. That’s because the __ notation is just syntax sugar for a constructor call to one of the &lt;font face="Courier New"&gt;XXXMessage&lt;/font&gt; classes.&lt;/p&gt;&lt;p&gt;Your comments are, as always, welcome. Thanks for reading.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-4114591115987761563?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/4114591115987761563/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/10/supporting-alternative-formatting.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/4114591115987761563'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/4114591115987761563'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/10/supporting-alternative-formatting.html' title='Supporting alternative formatting styles in logging'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-5680689820069012699</id><published>2010-10-26T00:03:00.001-07:00</published><updated>2010-10-26T00:12:57.433-07:00</updated><title type='text'>Shared state in logging: why?</title><content type='html'>&lt;p&gt;It’s generally recognised that undisciplined use of global variables is a warning sign about problems with a design. In fact, the paper “Global Variables Considered Harmful” by Wulf and Shaw dates back to 1973. The word “global” and the phrase “global state” have therefore acquired bad connotations in many people’s minds, and that can lead to situations where you “throw the baby out with the bath-water”.&lt;/p&gt;  &lt;p&gt;The concept of state &lt;em&gt;shared&lt;/em&gt; between multiple software components doesn’t have to be treated in the same way as naïve usage of global variables. There are legitimate situations where state needs to be shared – you will almost certainly be using a computer with a display, a mouse and a keyboard, all of which are essentially shared state (meaning data, and operations on that data, too) for the windowing system your computer is using.&lt;/p&gt;  &lt;p&gt;Python’s logging package uses some shared state; &lt;font face="Courier New"&gt;Logger&lt;/font&gt; objects are shared by all Python code in a single process, in that a call to &lt;font face="Courier New"&gt;logging.getLogger('foo.bar')&lt;/font&gt; will always return the same object, no matter where it’s called from. The fact that &lt;font face="Courier New"&gt;Loggers&lt;/font&gt; are singletons is for a specific, considered reason, and not just because I was taking a design short-cut or didn’t know how to do it any other way.&lt;/p&gt;  &lt;p&gt;There are alternative logging libraries which make a point of the fact that their loggers are constructed anew each time, so that a call&lt;/p&gt;  &lt;div class="syntax"&gt;   &lt;pre&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'foo.bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;will return a new object every time. These systems perhaps use loggers just as a vehicle for delivering events via their&lt;font face="Courier New"&gt; debug()&lt;/font&gt;, &lt;font face="Courier New"&gt;info()&lt;/font&gt; etc. methods; for stdlib logging, &lt;font face="Courier New"&gt;Loggers&lt;/font&gt; are something more. They play a part in how an application developer, system administrator or support team member &lt;em&gt;controls&lt;/em&gt; the logging verbosity of an application. This is done through configuration, which needs to consider the verbosity of all components used in an application - including third-party components whose source you are not free to modify.&lt;/p&gt;&lt;p&gt;Logging verbosity is primarily controlled by configuring level, propagation behaviour and filters for a logger. You can, of course, also control it via configuring levels and filters on handlers; but the best way is to configure loggers where possible, because that avoids the overhead of dispatching events to handlers only to have them be thrown away due to the handler’s configuration.&lt;/p&gt;&lt;p&gt;The logging package &lt;em&gt;relies&lt;/em&gt; on loggers being shared across modules, in order to offer this configurability. If my application is using a third-party library which logs some events to a logger named &lt;font face="Courier New"&gt;foo.bar&lt;/font&gt;, and if I want to configure the verbosity of this logger - by setting its level, say – I can only do this if I can be sure that&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'foo.bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;will return the &lt;em&gt;same&lt;/em&gt; object to the library code, which logs to logger &lt;font face="Courier New"&gt;foo.bar&lt;/font&gt;, and to my configuration code, which sets that logger’s level to what I want. If the two calls to get that named logger were to return two entirely &lt;em&gt;different&lt;/em&gt; objects, then the &lt;font face="Courier New"&gt;setLevel()&lt;/font&gt; call made by my configuration code would not affect the object used internally by the library. This means that I would essentially lose some level of control over logging verbosity.&lt;/p&gt;&lt;p&gt;When developers are essentially logging just for themselves – i.e. they are the primary, perhaps only, audience for their logging output – then these considerations may not matter too much. If they are not using third-party libraries which use logging, or using libraries whose logging verbosity they don’t care about, that may be another reason for not worrying about this loss of configurability. Since Python logging is intended for the whole Python developer community, some of whom will definitely need and want this level of configurability, the design supports it, using shared state. I know I use this feature myself regularly – say, when examining the SQL output from ORMs such as are provided by Django and SQLAlchemy. I don’t generally log the SQL output (which can be quite voluminous), but might do so as a result of coming across some problem, whether functional or performance-related. When the problem is identified, I can turn the logging of SQL statements off again. In larger application scenarios, this functionality is also available to those who have no means of changing the source code - application support teams or system administrators – assuming that the application developers have provided for this via the mechanisms offered by the logging package.&lt;/p&gt;&lt;p&gt;Another important (but related) design feature of Python logging is the use of hierarchical logger names. In stdlib logging, logger names are not just things which turn up in logging output. When you create a logger named &lt;font face="Courier New"&gt;foo.bar&lt;/font&gt;, you also conceptually create a logger named &lt;font face="Courier New"&gt;foo&lt;/font&gt;. This latter logger is not actually created unless and until a &lt;font face="Courier New"&gt;getLogger('foo')&lt;/font&gt; call is made, but the hierarchical namespace feature (and the way in which handler inheritance works with the logger hierarchy) allows you to turn logging verbosity up and down at multiple levels of a&amp;#160; package hierarchy quite easily – for example, turn on all logging in the &lt;font face="Courier New"&gt;django&lt;/font&gt; logging namespace, or just turn it on just for &lt;font face="Courier New"&gt;django.request&lt;/font&gt;, or perhaps just for &lt;font face="Courier New"&gt;django.db.backends&lt;/font&gt; – it puts the application developer/support team/sys admin in control, rather than just the library developer. And while the hierarchical feature might seem overkill for small projects and simple scripts, it doesn’t get in the way, and pays dividends when logging is used in larger systems.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-5680689820069012699?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/5680689820069012699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/10/shared-state-in-logging-why.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5680689820069012699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5680689820069012699'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/10/shared-state-in-logging-why.html' title='Shared state in logging: why?'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-6967797826571107279</id><published>2010-10-20T13:55:00.001-07:00</published><updated>2010-10-20T13:55:26.544-07:00</updated><title type='text'>StreamHandler’s newline terminator now configurable</title><content type='html'>&lt;p&gt;When &lt;font face="Courier New"&gt;StreamHandler&lt;/font&gt; writes a formatted log message to its stream, it adds a newline terminator. This behaviour is inherited by &lt;font face="Courier New"&gt;FileHandler&lt;/font&gt; and the other classes which derive from it (such as the rotating file handlers).&lt;/p&gt;  &lt;p&gt;For most people, that’s what they want, since they get log messages on separate lines without having to explicitly code newlines in their log messages. However, some people want the flexibility to not have this newline automatically appended to logged messages.&lt;/p&gt;  &lt;p&gt;Starting with Python 3.2, the message terminator will be configurable. This has been done by adding a &lt;font face="Courier New"&gt;terminator&lt;/font&gt; attribute to &lt;font face="Courier New"&gt;StreamHandler&lt;/font&gt;, which when emitting an event now writes the formatted message to its stream first, and then writes the terminator. If you don’t want newline termination for a handler, just set the handler instance’s &lt;font face="Courier New"&gt;terminator&lt;/font&gt; attribute to the empty string.&lt;/p&gt;  &lt;p&gt;There’s a small chance that this approach will cause unexpected behaviour – in the unlikely instance that someone is setting a &lt;font face="Courier New"&gt;terminator&lt;/font&gt; attribute on an a &lt;font face="Courier New"&gt;StreamHandler&lt;/font&gt; or one of its subclasses for their own, unrelated purposes. Other than that, the new behaviour should be backwards compatible.&lt;/p&gt;  &lt;p&gt;If this change is likely to adversely affect you for any reason, please let me know, by replying to this post. The change has already been checked into the py3k branch, and you are welcome to try it out.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-6967797826571107279?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/6967797826571107279/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/10/streamhandlers-newline-terminator-now.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6967797826571107279'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6967797826571107279'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/10/streamhandlers-newline-terminator-now.html' title='StreamHandler’s newline terminator now configurable'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-5368837884039138239</id><published>2010-10-20T04:50:00.001-07:00</published><updated>2010-10-20T04:50:26.527-07:00</updated><title type='text'>Implementing filters using callables</title><content type='html'>&lt;p&gt;It’s been pointed out that for implementing logging filters, you shouldn’t &lt;em&gt;have&lt;/em&gt; to go to the lengths of creating a &lt;font face="Courier New"&gt;Filter&lt;/font&gt; class just to define its one &lt;font face="Courier New"&gt;filter&lt;/font&gt; method. Of course, there are cases where you will need to use &lt;font face="Courier New"&gt;Filter&lt;/font&gt; classes, but there are also cases where it would be beneficial to just use a function. To cater for this, changes have been made to Python 3.2. Once this enters beta, you will be able to pass a function or other callable to the&amp;#160; &lt;font face="Courier New"&gt;addFilter&lt;/font&gt; and &lt;font face="Courier New"&gt;removeFilter&lt;/font&gt; methods of &lt;font face="Courier New"&gt;Handler&lt;/font&gt; and &lt;font face="Courier New"&gt;Logger&lt;/font&gt;, and this will be called with the &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; as its single argument to decide whether the event is to be logged.&lt;/p&gt;  &lt;p&gt;The previous functionality did not actually &lt;em&gt;require&lt;/em&gt; you to create &lt;font face="Courier New"&gt;Filter&lt;/font&gt; subclasses – you could pass any instance that had a &lt;font face="Courier New"&gt;filter&lt;/font&gt; method with the appropriate semantics. The &lt;font face="Courier New"&gt;Filter&lt;/font&gt; class was an illustration of how to do filtering as well as providing a not uncommon use case – filtering out everything other than a specific sub-tree in the logger namespace.&lt;/p&gt;  &lt;p&gt;However, with this change, you’re not forced to call your filtering method &lt;font face="Courier New"&gt;filter&lt;/font&gt;, so you could e.g. use different filtering methods in a single class, or different functions in a module.&lt;/p&gt;  &lt;p&gt;The &lt;a href="http://docs.python.org/dev/library/logging.html#filter-objects"&gt;development documentation&lt;/a&gt; has been updated to reflect this change.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-5368837884039138239?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/5368837884039138239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/10/implementing-filters-using-callables.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5368837884039138239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5368837884039138239'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/10/implementing-filters-using-callables.html' title='Implementing filters using callables'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-3295951790048050681</id><published>2010-09-27T03:36:00.001-07:00</published><updated>2010-09-27T04:21:08.285-07:00</updated><title type='text'>Unit testing and logging</title><content type='html'>&lt;p&gt;I’ve had questions in the past about using logging and unit testing together – what’s the best way of doing it?&lt;/p&gt;  &lt;p&gt;Beyond the choice of unit testing frameworks (e.g. &lt;font face="Courier New"&gt;unittest&lt;/font&gt;, &lt;font face="Courier New"&gt;nose&lt;/font&gt;, &lt;font face="Courier New"&gt;py.test&lt;/font&gt;), unit testing appears to be one of those things (like configuration) where people have strong, and differing, opinions about how they like to do it. This is one reason there is no specific support for unit testing in logging. Besides, logging is one area where functionally, your application should work in exactly the same way no matter how logging is configured, and even if logging is completely switched off (because there are no handlers configured, or the verbosity thresholds are set to be above CRITICAL). I am of course distinguishing here between &lt;em&gt;application&lt;/em&gt; logging and &lt;em&gt;request&lt;/em&gt; logging as practiced by web servers – the latter is part of the &lt;em&gt;function&lt;/em&gt; of the web server, whereas application logging is meant as a operational and diagnostic aid for developers, system administrators and support teams, and orthogonal to the application’s functionality. There are of course scenarios where logging is used to output messages to end users (typically, INFO or WARNING level messages), but it should be borne in mind that the application should keep going even if logging verbosity is turned off.&lt;/p&gt;  &lt;p&gt;There are two things that would be useful when using logging and unit testing together:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Some way of capturing all the messages which are logged by the code being unit tested. &lt;/li&gt;    &lt;li&gt;Some way of verifying that certain expectations with respect to logged messages are met. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Logging already provides a &lt;font face="Courier New"&gt;&lt;a href="http://docs.python.org/library/logging.html#logging.handlers.BufferingHandler"&gt;BufferingHandler&lt;/a&gt;&lt;/font&gt; class which allows you to capture &lt;font face="Courier New"&gt;LogRecords&lt;/font&gt; generated by logging activity. You can, for example, subclass this to store the &lt;font face="Courier New"&gt;LogRecord.__dict__&lt;/font&gt; values rather than the &lt;font face="Courier New"&gt;LogRecords&lt;/font&gt; themselves – this will facilitate checking whether expectations are met. You typically don’t want to flush anything until the end of the test, though, so a handler which facilitates testing might look like this:&lt;/p&gt;  &lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logging.handlers&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BufferingHandler&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BufferingHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c"&gt;# BufferingHandler takes a &amp;quot;capacity&amp;quot; argument&lt;/span&gt;
        &lt;span class="c"&gt;# so as to know when to flush. As we're overriding&lt;/span&gt;
        &lt;span class="c"&gt;# shouldFlush anyway, we can set a capacity of zero.&lt;/span&gt;
        &lt;span class="c"&gt;# You can call flush() manually to clear out the&lt;/span&gt;
        &lt;span class="c"&gt;# buffer.&lt;/span&gt;
        &lt;span class="n"&gt;BufferingHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shouldFlush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now let’s consider the checking of expectations about what’s been logged. We could implement this in the &lt;font face="Courier New"&gt;TestHandler&lt;/font&gt; class directly, but it’s the sort of area where different people may want to do different things. The bare minimum we need in &lt;font face="Courier New"&gt;TestHandler&lt;/font&gt; would be something that looks for some kind of match between what’s been logged (the buffer of dictionaries) and the expected values. So as an idea, let’s delegate the details of matching to a separate &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; class, which must have a &lt;font face="Courier New"&gt;matches&lt;/font&gt; method. Because matching of dictionaries is likely to crop up in tests other than to do with logging, creating a separate &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; class allows us to deploy the functionality in other scenarios. So, we can develop the &lt;font face="Courier New"&gt;TestHandler&lt;/font&gt; class a little:&lt;/p&gt;&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logging.handlers&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BufferingHandler&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BufferingHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c"&gt;# BufferingHandler takes a &amp;quot;capacity&amp;quot; argument&lt;/span&gt;
        &lt;span class="c"&gt;# so as to know when to flush. As we're overriding&lt;/span&gt;
        &lt;span class="c"&gt;# shouldFlush anyway, we can set a capacity of zero.&lt;/span&gt;
        &lt;span class="c"&gt;# You can call flush() manually to clear out the&lt;/span&gt;
        &lt;span class="c"&gt;# buffer.&lt;/span&gt;
        &lt;span class="n"&gt;BufferingHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shouldFlush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Look for a saved dict whose keys/values match the supplied arguments.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The signature for the &lt;font face="Courier New"&gt;matches&lt;/font&gt; method allows us to pass just the keys we want to test for in the call. In implementing the &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; class, we could do whatever we wanted – regular expression matching, for example – but we’ll keep it simple. Let’s assume that we want to either match values exactly, or else do partial matches for string values such as messages. (In order to ensure that a formatted message appears in a &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt;’s &lt;font face="Courier New"&gt;__dict__&lt;/font&gt;, we need to call &lt;font face="Courier New"&gt;self.format(record)&lt;/font&gt; in the &lt;font face="Courier New"&gt;emit&lt;/font&gt; method.)&lt;/p&gt;&lt;p&gt;To decide which keys to do partial matches on, we can store a set of the relevant keys in a &lt;font face="Courier New"&gt;_partial_matches&lt;/font&gt; attribute. This is defined in the &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; class, but you can replace it in a subclass or even an instance of &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; if you need to. Here’s the &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; class:&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Matcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;_partial_matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'msg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Try to match a single dict with the supplied arguments.&lt;/span&gt;

&lt;span class="sd"&gt;        Keys whose values are strings and which are in self._partial_matches&lt;/span&gt;
&lt;span class="sd"&gt;        will be checked for partial (i.e. substring) matches. You can extend&lt;/span&gt;
&lt;span class="sd"&gt;        this scheme to (for example) do regular expression matching, etc.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;dv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;match_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Try to match a single stored value (dv) with a supplied value (v).&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_partial_matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Notice that the class is independent of logging and might prove useful elsewhere. Also note the use of &lt;font face="Courier New"&gt;str&lt;/font&gt; in the &lt;font face="Courier New"&gt;match_value&lt;/font&gt; method – you may want to replace this with &lt;font face="Courier New"&gt;basestring&lt;/font&gt; for Python 2.x. (Of course, I could have used &lt;font face="Courier New"&gt;isinstance&lt;/font&gt;, too.)&lt;/p&gt;&lt;p&gt;Now we can consider what a typical test case using &lt;font face="Courier New"&gt;TestHandler&lt;/font&gt; and &lt;font face="Courier New"&gt;Matcher&lt;/font&gt; might look like (some imports omitted):&lt;/p&gt;&lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Matcher&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_simple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;&amp;quot;Simple test of logging test harness.&amp;quot;&lt;/span&gt;
        &lt;span class="c"&gt;# Just as a demo, let's log some messages.&lt;/span&gt;
        &lt;span class="c"&gt;# Only one should show up in the log.&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;This won't show up.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Neither will this.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;But this will.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;&amp;quot;Test of partial matching in logging test harness.&amp;quot;&lt;/span&gt;
        &lt;span class="c"&gt;# Just as a demo, let's log some messages.&lt;/span&gt;
        &lt;span class="c"&gt;# Only one should show up in the log.&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;This won't show up.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Neither will this.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;But this will.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ut th&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;# from &amp;quot;But this will&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ut th&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;# from &amp;quot;But this will&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;either&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;won't&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_multiple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;&amp;quot;Test of matching multiple values in logging test harness.&amp;quot;&lt;/span&gt;
        &lt;span class="c"&gt;# Just as a demo, let's log some messages.&lt;/span&gt;
        &lt;span class="c"&gt;# Only one should show up in the log.&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;This won't show up.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Neither will this.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;But this will.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;And so will this.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="n"&gt;funcName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'test_multiple'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="n"&gt;funcName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'test_multiple'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;levelno&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The whole script is &lt;a href="http://gist.github.com/598834"&gt;here&lt;/a&gt;, and if you run it, you should have no errors:&lt;/p&gt;&lt;pre&gt;vinay@eta-jaunty:~/projects/scratch$ python2.7 testhdlr.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK
vinay@eta-jaunty:~/projects/scratch$ python3.2 testhdlr.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK&lt;/pre&gt;&lt;p&gt;Is there anything I’ve missed? Anything you don’t like about these suggestions? Please feel free to comment. Thanks for reading.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-3295951790048050681?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/3295951790048050681/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/unit-testing-and-logging.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/3295951790048050681'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/3295951790048050681'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/unit-testing-and-logging.html' title='Unit testing and logging'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-2411126611160086088</id><published>2010-09-26T01:47:00.001-07:00</published><updated>2010-09-26T17:08:36.820-07:00</updated><title type='text'>"Too long; didn’t read"</title><content type='html'>&lt;p&gt;Someone asked: what’s with the &lt;strong&gt;tl;dr&lt;/strong&gt; at the start of every post? My first reaction was surprise that they’d even read any of these posts. After all, logging is a prosaic, even mundane subject – well beneath the radar for many developers. There’s definitely more fun to be had thinking about NoSQL databases, or high availability, or templating systems, or web application frameworks, or (your favourite topic here).&lt;/p&gt;  &lt;p&gt;But people do arrive here via search engines, not knowing what they’ll find. Even when people actually come here looking for information about logging, there’s no reason to suppose that every post is of (equal) interest to them.&lt;/p&gt;  &lt;p&gt;In these days of ever-shortening attention spans, many people are only interested in tweet-sized chunks, but unfortunately there are some things I can’t cover in 140 chars or less. A reasonable treatment of some topics does require some descent into detail; and, while developers are generally masters of detail, they are often picky about &lt;em&gt;which&lt;/em&gt; details they want to be bothered with. You’ll also see that I’m somewhat inexperienced at writing blog posts – though I do hope to get better over time, my writing style might seem more than a little prolix to some.&lt;/p&gt;  &lt;p&gt;So it seems like simple courtesy to advertise at the outset what a post-with-detail is about, so as to avoid wasting &lt;em&gt;your&lt;/em&gt; time if the topic is of no interest to you. It’s just like the abstract you get at the head of academic papers. While it might seem whimsical to call it a tl; dr, it definitely seems pretentious to call it an abstract. To be fair, I don’t put it at the start of &lt;em&gt;every&lt;/em&gt; post :-)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-2411126611160086088?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/2411126611160086088/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/too-long-didnt-read.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2411126611160086088'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/2411126611160086088'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/too-long-didnt-read.html' title='&quot;Too long; didn’t read&quot;'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-319984519049538943</id><published>2010-09-24T06:58:00.001-07:00</published><updated>2010-09-25T10:21:16.306-07:00</updated><title type='text'>Integrating logging with diverse notification services</title><content type='html'>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: Nowadays, there is a growing number of ways in which people prefer to receive notifications about happenings of interest. While Python logging cannot provide, out-of-the-box, handlers that allow you to send notifications to all of these different systems, the basic functionality provided in the stdlib makes it fairly easy for developers to support the notification mechanisms that are preferred by their audiences. This post will give some examples of how this can be economically achieved.&lt;/p&gt;  &lt;p&gt;Logging is basically about getting information about things that happen in your software (whether application or library) out to an interested audience. The diversity of ways in which that audience wants to receive information is growing all the time: from the earliest days of being limited to email and pagers, people have progressed to a using a plethora of social networks, IM and mobile phone platforms, different desktop notification systems - and the list keeps growing. Even if the number of basic &lt;em&gt;types&lt;/em&gt; of notification method grows slowly, the number of individual &lt;em&gt;instances&lt;/em&gt; of those types can grow faster. For example, if you just look at a subset of desktop notification methods, you have Growl (&lt;a href="http://www.growl.info/"&gt;OS X&lt;/a&gt;, &lt;a href="http://www.growlforwindows.com/gfw/"&gt;Windows&lt;/a&gt;), &lt;a href="http://www.fullphat.net/"&gt;Snarl&lt;/a&gt; (Windows), &lt;a href="http://library.gnome.org/devel/libnotify/"&gt;libnotify&lt;/a&gt; and &lt;a href="http://www.mumbles-project.org/"&gt;mumbles&lt;/a&gt; (Linux). The &lt;a href="http://en.wikipedia.org/wiki/Instant_messaging"&gt;Wikipedia page for Instant messaging&lt;/a&gt; lists (at the time of writing) over 20 different IM systems, some of which are only used in particular parts of the world. Likewise, for social networks one might think first of Facebook, Bebo or Twitter, but there are many others which are very popular, but in different parts of the world (for example, Orkut in South and Central America and parts of Asia).&lt;/p&gt;  &lt;p&gt;How can you, as a developer who uses logging in your library or application, take account of your users’ preferences when getting information out to them? Of course, the large majority of logging messages will be sent to console or file. However, there will be certain messages in the ERROR or CRITICAL category which need to be sent to people so that urgent action to be taken. These have generally been sent by email, but there are circumstances when you might want to use alternative mechanisms (or even multiple mechanisms) to get information out so that it can be received as soon as possible and (hopefully) acted on in a timely fashion.&lt;/p&gt;  &lt;p&gt;Clearly, it’s not practical for the logging package in the stdlib to provide native support for the myriad specialized ways of sending notifications which exist today. Logging currently provides 14 handlers (not including base classes), just about all of which relate to basic infrastructure communication or storage mechanisms:&lt;/p&gt;  &lt;table style="width: 100%" class="comparison" cellspacing="0" cellpadding="0" width="835"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;NullHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used by libraries to avoid misconfiguration messages&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;StreamHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to streams&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;FileHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to disk files&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;RotatingFileHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to size-based rotating log files&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;TimedRotatingFileHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to time-based rotating log files&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;WatchedFileHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;A &lt;font face="Courier New"&gt;FileHandler&lt;/font&gt; which supports external log file rotation mechanisms&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;SocketHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to TCP sockets&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;DatagramHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to UDP sockets&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;SysLogHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to a syslog daemon via either UDP or Unix domain sockets&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;SMTPHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to send emails&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;NTEventLogHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to write to Windows NT event logs&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;HTTPHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to send to arbitrary web sites&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;&lt;font face="Courier New"&gt;MemoryHandler&lt;/font&gt;&lt;/td&gt;        &lt;td&gt;Used to buffer up events and process in batches&lt;/td&gt;        &lt;td&gt;&lt;font face="Courier New"&gt;QueueHandler&lt;/font&gt; (3.2)&lt;/td&gt;        &lt;td&gt;Used to send to in-process or &lt;font face="Courier New"&gt;multiprocessing&lt;/font&gt; queues&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;As you can see, all of these are very generic and relate to storage mechanisms and protocols which are unlikely to go out of fashion any time soon.&lt;/p&gt;  &lt;p&gt;However, you don’t need to worry unduly if you want to send notifications using some of the newer facilities available today. Of course you can use something like &lt;font face="Courier New"&gt;HTTPHandler&lt;/font&gt; (subclassing it if necessary) to send information to any web site, but you may not see any support for e.g. desktop notification systems. How can logging provide this?&lt;/p&gt;  &lt;p&gt;It doesn’t make much sense for me to put in “native” support for multiple notification protocols into the stdlib, for a number of reasons:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The methods, protocols and sites which are popular today may not be popular tomorrow. Any support added for them in the stdlib would have to live on for a very long time, even if their popularity waned. &lt;/li&gt;    &lt;li&gt;The methods, protocols and sites may keep changing how they work, leading to an increased maintenance burden. &lt;/li&gt;    &lt;li&gt;Python is used globally, and people would (understandably) expect to have support for popular systems in their neck of the woods – which may not be easy if, for example, all the documentation about the protocols used is inadequate or in a foreign language I’m not familiar with. &lt;/li&gt;    &lt;li&gt;Native support of external protocols may require additional dependencies, which is not an appropriate burden for Python to carry, or use third-party modules which give rise to warnings or errors (for example, some Python libraries for Growl use the deprecated &lt;font face="Courier New"&gt;md5&lt;/font&gt; module, which can give rise to deprecation warnings – this requires code in the libraries or applications using them to turn those warnings off). &lt;/li&gt;    &lt;li&gt;Even if native support is provided for various systems, there are many options that are available when using such systems.The API for stdlib facilities to support such flexibility in options may be unwieldy, and even if that isn’t the case, it will certainly evolve over time, likely requiring you to subclass and change things, or fork and modify if the original classes weren’t reusable via subclassing. So, any idea of a long-lived “out-of-the-box” solution which requires no work from you is possibly an impossible dream :-( &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Of course, developers could write “native” handlers for systems and upload them to PyPI (or post them elsewhere), and indeed people have done this in the past. That may be an approach that works for you; but anyone can upload their offerings to PyPI, which leads to what has been called the &lt;a href="http://utcc.utoronto.ca/~cks/space/blog/python/WhyInStandardLibrary"&gt;selection problem&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Nevertheless, some people might be thinking that some support for these newer notification systems would be nice to have with stdlib logging. What’s a good way of achieving this? We should take inspiration from the &lt;a href="http://en.wikipedia.org/wiki/Unix_philosophy"&gt;Unix philosophy&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Write programs that do one thing and do it well. Write programs to work together.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To apply this philosophy to the problem at hand, we can take advantage of the fact that many of these notification systems have &lt;em&gt;command line interfaces&lt;/em&gt;! This is true at least on Unix and Unix-like systems, meaning primarily Linux and OS X. Here are some examples:&lt;/p&gt;  &lt;table style="width: 100%" cellspacing="0" cellpadding="0"&gt;&lt;tbody&gt;     &lt;tr class="secthead"&gt;       &lt;td&gt;&lt;strong&gt;&lt;font size="3"&gt;Twitter&lt;/font&gt;&lt;/strong&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;Before Twitter changed their authentication system to disallow Basic authentication, you could just use curl like this:          
          
&lt;font face="Courier New"&gt;curl –u username:password -d status=&amp;quot;Hello, world!&amp;quot; http://twitter.com/statuses/update.json&lt;/font&gt;           
          
Now that Twitter has switched to using OAuth, things are not quite &lt;em&gt;so&lt;/em&gt; easy, but they can be after a small amount of one-time set-up.           
          &lt;h4&gt;One-time setup&lt;/h4&gt; We’ll use Mike Verdone’s &lt;a href="http://mike.verdone.ca/twitter/"&gt;Python Twitter Tools&lt;/a&gt;. It’s as easy as doing &lt;font face="Courier New"&gt;easy_install twitter&lt;/font&gt; and waiting for the installation to complete. Then, just type &lt;font face="Courier New"&gt;twitter&lt;/font&gt; in the shell. You’ll see something like this:           
          &lt;p&gt;&lt;font face="Courier New"&gt;vinay@zeta-lucid:~/tools$ twitter              
Hi there! We're gonna get you all set up to use the Command-Line Tool. &lt;/font&gt;&lt;/p&gt;          &lt;p&gt;&lt;font face="Courier New"&gt;In the web browser window that opens please choose to Allow              
access. Copy the PIN number that appears on the next page and paste or               
type it here: &lt;/font&gt;&lt;/p&gt;          &lt;p&gt;&lt;font face="Courier New"&gt;Please enter the PIN:&lt;/font&gt;&amp;#160;&lt;/p&gt;          &lt;p&gt;A browser window opens on the Twitter website, and you enter your username and password into the presented form and click “Allow” to submit. A numeric PIN code is displayed in the response page, which you type or paste into the shell at the above prompt. You’re then told:&lt;/p&gt;          &lt;p&gt;&lt;font face="Courier New"&gt;That's it! Your authorization keys have been written to /home/vinay/.twitter_oauth.&lt;/font&gt;&lt;/p&gt; and that’s the end of the one-time setup. You can type &lt;font face="Courier New"&gt;twitter –-help&lt;/font&gt; to find the commands options available via the tool; they’ll fit most people’s needs.           
          &lt;h4&gt;Regular usage&lt;/h4&gt; Once you’ve done the above setup, you can just use a command line like           
          
&lt;font face="Courier New"&gt;twitter set hello&lt;/font&gt;           
          
to update your status on Twitter:           
          
&lt;a href="http://lh6.ggpht.com/_-N8xZeqqzq4/TJyugyPqtfI/AAAAAAAABIw/s8EmwAlpQw0/s1600-h/twit%5B2%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="Twitter screenshot" border="0" alt="Twitter screenshot" src="http://lh3.ggpht.com/_-N8xZeqqzq4/TJyuhV8ldAI/AAAAAAAABI0/WHpIK0-7BSw/twit_thumb.png?imgmax=800" width="244" height="138" /&gt;&lt;/a&gt;           
&lt;/td&gt;     &lt;/tr&gt;      &lt;tr class="secthead"&gt;       &lt;td&gt;&lt;font size="3"&gt;&lt;strong&gt;libnotify&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;To use &lt;font face="Courier New"&gt;libnotify&lt;/font&gt; from the command-line, use the &lt;font face="Courier New"&gt;notify-send&lt;/font&gt; program, which is part of &lt;font face="Courier New"&gt;libnotify&lt;/font&gt;. (On Ubuntu, you should be able to do &lt;font face="Courier New"&gt;sudo apt-get install notify-bin&lt;/font&gt; to install it). The &lt;font face="Courier New"&gt;man&lt;/font&gt; page will tell you all you need to know, but a simple usage would be           
          
&lt;font face="Courier New"&gt;notify-send title message –I icon_path            
&lt;/font&gt;          
You can set the urgency, time limit etc. through command-line parameters.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr class="secthead"&gt;       &lt;td&gt;&lt;font size="3"&gt;&lt;strong&gt;Growl&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;You can use the &lt;font color="#000000" face="Courier New"&gt;&lt;a href="http://growl.info/extras.php#growlnotify"&gt;growlnotify&lt;/a&gt;&lt;/font&gt; program which is part of Growl. This has numerous options, but a simple usage would be           
          
&lt;font face="Courier New"&gt;growlnotify –n appname –m message –I iconpath &lt;/font&gt;          
          
You can set priority, stickiness etc. using various command-line parameters.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr class="secthead"&gt;       &lt;td&gt;&lt;font size="3"&gt;&lt;strong&gt;Mumbles&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;If you use Mumbles, you can use the &lt;font face="Courier New"&gt;mumbles-send&lt;/font&gt; script which comes with it, for example as follows.           
          
&lt;font face="Courier New"&gt;mumbles-send title message            
&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;&lt;tr class="secthead"&gt;       &lt;td&gt;&lt;font size="3"&gt;&lt;strong&gt;Snarl&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;If you use Snarl, you can use the &lt;a href="http://sourceforge.net/projects/mozillasnarls/files/Snarl_CMD/1.0/Snarl_CMD_1.0.zip/download"&gt;&lt;font face="Courier New"&gt;Snarl_CMD.exe&lt;/font&gt;&lt;/a&gt; program, for example as follows.           
          
&lt;font face="Courier New"&gt;Snarl_CMD nShowMessage TIME TITLE BODY [iconPATH]           
&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr class="secthead"&gt;       &lt;td&gt;&lt;font size="3"&gt;&lt;strong&gt;Other Web-based notification services (for example, ticketing systems)&lt;/strong&gt;&lt;/font&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p&gt;For Web-based services in general, you may be able to use &lt;font face="Courier New"&gt;logging.handlers.HTTPHandler&lt;/font&gt; either directly or via subclassing, but if you want to look at a command-line based solution which is similar to the ones above, you can use the excellent &lt;a href="http://curl.haxx.se/"&gt;curl&lt;/a&gt; command-line tool:&lt;/p&gt;          &lt;blockquote&gt;           &lt;p&gt;curl is a command line tool for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, kerberos...), file transfer resume, proxy tunnelling and a busload of other useful tricks.&lt;/p&gt;         &lt;/blockquote&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;You might be thinking that it seems a bit clunky in this day and age to be using the command-line in this way, and you may be right to think so. But it represents a straightforward, practical approach to integrating logging with many popular notification services in a way which doesn’t load the stdlib with unnecessary baggage. Plus, it may well involve less work for you: for example, you might be able to use a single command-line handler class, configured with various different command lines for different notification methods. Less code is good! And while in the past there was less than an ideal level of support under Windows for command-line tools, perhaps the development of PowerShell shows that the situation is changing for the better.&lt;/p&gt;  &lt;p&gt;Perhaps you’re concerned about the overhead of running external command-line programs in subprocesses and the delays this might cause; in that case, you might want to look at this &lt;a href="http://plumberjack.blogspot.com/2010/09/improved-queuehandler-queuelistener.html"&gt;earlier post&lt;/a&gt; which explains how you can use &lt;font face="Courier New"&gt;QueueHandler&lt;/font&gt; and &lt;font face="Courier New"&gt;QueueListener&lt;/font&gt; classes to delegate the heavy lifting to separate threads or processes, leaving your Web application threads (and other performance-critical threads) as responsive as possible.&lt;/p&gt;  &lt;p&gt;One possible realization of a command-line handler might be:&lt;/p&gt;  &lt;div class="syntax"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommandLineHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    A class which executes parametrized command lines in a separate process.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Initialize an instance with a command-line template.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        The template consists of a set of strings, some of which may contain&lt;/span&gt;
&lt;span class="sd"&gt;        variable content merged in from a LogRecord.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        The LogRecord merge will be done before executing as a command.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmdline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmdline&lt;/span&gt;
        
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Handle a record.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        This just merges its data into the command-line template and&lt;/span&gt;
&lt;span class="sd"&gt;        executes the resulting command.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;cmdline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Execute the specified command&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Recall from the &lt;a href="http://plumberjack.blogspot.com/2010/09/improved-queuehandler-queuelistener.html"&gt;earlier post&lt;/a&gt; that &lt;font face="Courier New"&gt;QueueListener&lt;/font&gt; can be given (as a handler) any object which has a &lt;font face="Courier New"&gt;handle&lt;/font&gt; method: so a &lt;font face="Courier New"&gt;CommandLineHandler&lt;/font&gt; instance could be used as a handler.&lt;/p&gt;&lt;p&gt;Of course, you should very careful with any facility which executes potentially arbitrary commands on your server, making sure that command line configuration is covered by security diligence. An example command line as passed to the constructor might be &lt;font face="Courier New"&gt;['notify-send', '%(appname)s - %(name)s', '%(message)s']&lt;/font&gt; where &lt;font face="Courier New"&gt;appname&lt;/font&gt; is a context value inserted in the &lt;font face="Courier New"&gt;LogRecord&lt;/font&gt; (e.g. by a &lt;font face="Courier New"&gt;Filter&lt;/font&gt;, see &lt;a href="http://docs.python.org/library/logging.html#adding-contextual-information-to-your-logging-output"&gt;this documentation&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;I’d be grateful to get any feedback about the ideas expressed in this post. If you got this far, thanks for reading :-)&lt;/p&gt;&lt;hr /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-319984519049538943?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/319984519049538943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/integrating-logging-with-diverse.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/319984519049538943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/319984519049538943'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/integrating-logging-with-diverse.html' title='Integrating logging with diverse notification services'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_-N8xZeqqzq4/TJyuhV8ldAI/AAAAAAAABI0/WHpIK0-7BSw/s72-c/twit_thumb.png?imgmax=800' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-8200437195199374385</id><published>2010-09-22T07:06:00.001-07:00</published><updated>2010-09-22T11:29:16.098-07:00</updated><title type='text'>Improved QueueHandler, QueueListener: dealing with handlers that block</title><content type='html'>&lt;b&gt;tl; dr&lt;/b&gt;: Sometimes you have to get your logging handlers to do their work without blocking the thread you’re logging from. This post discusses one way in which you can do that, and presents a new &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; class as well as some improvements in the &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; class discussed in earlier posts. A &lt;a href="http://gist.github.com/591699"&gt;working script&lt;/a&gt; is provided so you can play with these ideas yourself.&lt;br /&gt;
&lt;br /&gt;
Sometimes, you’ll work with code that runs in threads which must do their processing quickly. This is common in Web applications, though of course it also occurs in other scenarios. You may well need to log messages from those threads; however, if you are a library developer, you typically won’t have control over which handlers are configured by the running application. What happens if some of the configured handlers are ones that potentially will block for longish periods of time, causing delays which hold the threads up longer than is acceptable? &lt;br /&gt;&lt;br/&gt;
The most common culprit which demonstrates sluggish behaviour is the &lt;span style="font-family: Courier New;"&gt;SMTPHandler&lt;/span&gt;: sending emails can take a long time, for a number of reasons outside the developer’s control (for example, a poorly performing mail or network infrastructure). But almost any network-based handler can block: Even a &lt;span style="font-family: Courier New;"&gt;SocketHandler&lt;/span&gt; operation may do a DNS query under the hood which is too slow (and this query can be deep in the socket library code, below the Python layer, and outside your control).&lt;br /&gt;
&lt;br /&gt;
One answer which pushes itself to the fore is to arrange things so that the time-consuming operation happens on a separate thread. This has been suggested to me once or twice, and suggestions have been made to perhaps provide threaded versions of the existing &lt;span style="font-family: Courier New;"&gt;Handler&lt;/span&gt; classes or incorporate threading into some of the existing classes. That didn’t sound like a great idea to me, and for one reason or another I haven’t been able to address it until now.&lt;br /&gt;&lt;br /&gt;
I &lt;a href="http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html"&gt;recently posted&lt;/a&gt; about an addition to Python 3.2 of a &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; class, the addition of which came about as a result of Mike Bayer of SQLAlchemy fame pointing me to a &lt;a href="http://stackoverflow.com/questions/641420/how-should-i-log-while-using-multiprocessing-in-python/894284#894284"&gt;Stack Overflow answer&lt;/a&gt; he had given, to a question about using &lt;span style="font-family: Courier New;"&gt;logging&lt;/span&gt; with &lt;span style="font-family: Courier New;"&gt;multiprocessing&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
Mike’s solution was a specialized handler for use with &lt;span style="font-family: Courier New;"&gt;multiprocessing&lt;/span&gt; which delegated to a &lt;span style="font-family: Courier New;"&gt;RotatingFileHandler&lt;/span&gt;, which worked for his use case but was not general enough to put in the stdlib. So I came up with &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt;, which works not only with &lt;span style="font-family: Courier New;"&gt;multiprocessing&lt;/span&gt; queues but also in-process thread-safe queues as implemented in the &lt;span style="font-family: Courier New;"&gt;Queue&lt;/span&gt; module (renamed to &lt;span style="font-family: Courier New;"&gt;queue&lt;/span&gt; in more recent Pythons). &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; is also easy to subclass, for example to send logging events to a ZeroMQ socket, as described in &lt;a href="http://plumberjack.blogspot.com/2010/09/queuehandler-and-zeromq-support.html"&gt;this post&lt;/a&gt;.&lt;br /&gt;
&lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; also forms the basis of dealing with handlers that block. Before we look at one way to solve that problem, I should mention that the &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; implementation which will be in 3.2 got a slight improvement: a new &lt;span style="font-family: Courier New;"&gt;prepare&lt;/span&gt; method was added and the &lt;span style="font-family: Courier New;"&gt;emit&lt;/span&gt; method was modified to use it. The docstrings give the reason for this minor refactoring:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Prepares a record for queuing. The object returned by this&lt;/span&gt;
&lt;span class="sd"&gt;        method is enqueued.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        The base implementation formats the record to merge the message&lt;/span&gt;
&lt;span class="sd"&gt;        and arguments, and removes unpickleable items from the record&lt;/span&gt;
&lt;span class="sd"&gt;        in-place.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        You might want to override this method if you want to convert&lt;/span&gt;
&lt;span class="sd"&gt;        the record to a dict or JSON string, or send a modified copy&lt;/span&gt;
&lt;span class="sd"&gt;        of the record while leaving the original intact.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="c"&gt;# The format operation gets traceback text into record.exc_text&lt;/span&gt;
        &lt;span class="c"&gt;# (if there's exception data), and also puts the message into&lt;/span&gt;
        &lt;span class="c"&gt;# record.message. We can then use this to replace the original&lt;/span&gt;
        &lt;span class="c"&gt;# msg + args, as these might be unpickleable. We also zap the&lt;/span&gt;
        &lt;span class="c"&gt;# exc_info attribute, as it's no longer needed and, if not None,&lt;/span&gt;
        &lt;span class="c"&gt;# will typically not be pickleable.&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exc_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Emit a record.&lt;/span&gt;

&lt;span class="sd"&gt;        Writes the LogRecord to the queue, preparing it first.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The whole class is to be found &lt;a href="http://gist.github.com/591589"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Moving on to how to solve the problem of blocking handlers, the answer is in part to attach only a &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; to those loggers which are accessed from performance-critical threads. They simply write to their queue, which can be sized to a large enough capacity or initialized with no upper bound to their size. The write to the queue will typically be accepted quickly, though you will probably need to catch the &lt;span style="font-family: Courier New;"&gt;queue.Full&lt;/span&gt; exception as a precaution in your code. If you are a library developer who has performance-critical threads in their code, be sure to document this (together with a suggestion to attach only &lt;span style="font-family: Courier New;"&gt;QueueHandlers&lt;/span&gt; to your loggers) for the benefit of other developers who will use your code.&lt;br /&gt;
&lt;br /&gt;
The second part of the solution is &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt;, which has been designed as the counterpart to &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt;.&amp;nbsp; A &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; is very simple: it’s passed a queue and a handler, and it fires up an internal thread which listens to its queue for &lt;span style="font-family: Courier New;"&gt;LogRecords&lt;/span&gt; sent from &lt;span style="font-family: Courier New;"&gt;QueueHandlers&lt;/span&gt; (or any other source of &lt;span style="font-family: Courier New;"&gt;LogRecords&lt;/span&gt;, for that matter). It should be relatively easy to subclass &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; to listen using other types of queue – for example, ZeroMQ sockets – though we won’t cover that in this post (it’ll be using a similar approach as was described &lt;a href="http://plumberjack.blogspot.com/2010/09/queuehandler-and-zeromq-support.html"&gt;here&lt;/a&gt; for our &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; subclass &lt;span style="font-family: Courier New;"&gt;ZeroMQSocketHandler&lt;/span&gt;). Here’s &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueueListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;_sentinel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Initialise an instance with the specified queue and&lt;/span&gt;
&lt;span class="sd"&gt;        handler.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Dequeue a record and return it, optionally blocking.&lt;/span&gt;

&lt;span class="sd"&gt;        The base implementation uses get. You may want to override this method&lt;/span&gt;
&lt;span class="sd"&gt;        if you want to use timeouts or work with custom queue implementations.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Start the listener.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        This starts up a background thread to monitor the queue for&lt;/span&gt;
&lt;span class="sd"&gt;        LogRecords to process.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDaemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Monitor the queue for records, and ask the handler&lt;/span&gt;
&lt;span class="sd"&gt;        to deal with them.&lt;/span&gt;

&lt;span class="sd"&gt;        This method runs on a separate, internal thread.&lt;/span&gt;
&lt;span class="sd"&gt;        The thread will terminate if it sees a sentinel object in the queue.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;
        &lt;span class="n"&gt;has_task_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'task_done'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_stop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_sentinel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_task_done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="c"&gt;# There might still be records in the queue.&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_sentinel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_task_done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Stop the listener.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        This asks the thread to terminate, and then waits for it to do so.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_stop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_sentinel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;
Since the queue should only ever get &lt;span style="font-family: Courier New;"&gt;LogRecords&lt;/span&gt; put into it, it seems reasonable to use &lt;span style="font-family: Courier New;"&gt;None&lt;/span&gt; as a sentinel to terminate the thread. Of course, you can set a different sentinel if you wish.&lt;br /&gt;
&lt;br /&gt;
The advantage of having a separate &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; class is that you can use the same instance to service multiple &lt;span style="font-family: Courier New;"&gt;QueueHandlers&lt;/span&gt;. This is more resource-friendly than, say, having threaded versions of the existing handler classes, which would eat up one thread per handler for no particular benefit.&lt;br /&gt;
&lt;br /&gt;
Here’s a simple snippet showing how to use &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; and &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; together:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;qh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QueueListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%(threadName)s&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;# The log output will display the thread which generated&lt;/span&gt;
    &lt;span class="c"&gt;# the event (the main thread) rather than the internal&lt;/span&gt;
    &lt;span class="c"&gt;# thread which monitors the internal queue. This is what&lt;/span&gt;
    &lt;span class="c"&gt;# you want to happen.&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Look out!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;
This should be self-explanatory, but of course please feel free to post a comment if you need clarification of anything.&lt;br /&gt;
&lt;br /&gt;
The whole script is &lt;a href="http://gist.github.com/591699"&gt;here&lt;/a&gt;, and if you run it you should see something like the following:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;
MainThread: Look out!&lt;/blockquote&gt;
&lt;br /&gt;
Notice that &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; is not even especially logging-specific: You can pass it as a handler any object that has a &lt;span style="font-family: Courier New;"&gt;handle&lt;/span&gt; method which takes a single argument, and that method will be passed any non-sentinel object which appears on the queue.&lt;br /&gt;
&lt;br /&gt;
You should be able to paste &lt;span style="font-family: Courier New;"&gt;QueueHandler&lt;/span&gt; and &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; into your own code, as you may well be using Python versions earlier than 3.2 :-)&lt;br /&gt;
&lt;br /&gt;
The plan is to add &lt;span style="font-family: Courier New;"&gt;QueueListener&lt;/span&gt; to &lt;span style="font-family: Courier New;"&gt;logging.handlers&lt;/span&gt;, so before 3.2 enters beta, I’d be grateful for any comments or suggestions you have about any of this stuff.&lt;hr/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-8200437195199374385?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/8200437195199374385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/improved-queuehandler-queuelistener.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8200437195199374385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8200437195199374385'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/improved-queuehandler-queuelistener.html' title='Improved QueueHandler, QueueListener: dealing with handlers that block'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-233393633116721311</id><published>2010-09-19T04:25:00.000-07:00</published><updated>2010-09-19T04:26:20.085-07:00</updated><title type='text'>Python Logging Functionality - Facts vs. Myths</title><content type='html'>&lt;b&gt;tl; dr&lt;/b&gt;: Some inaccuracies and misunderstandings about how stdlib logging works have been expressed in the documentation and marketing presentations of a suggested alternative. On closer examination, certain statements which imply a lack of functionality or other shortcomings in stdlib logging have been shown to be inaccurate. You should feel confident that in using the stdlib logging package you are very unlikely to find it wanting, and that, if it seems too hard or not possible to achieve some result that you want to achieve, you should raise the issue on comp.lang.python or bugs.python.org and be assured of prompt responses and resolutions. Now, you can read on if you want more details :-).&lt;br /&gt;
&lt;br /&gt;
Recently Armin Ronacher, developer of Werkzeug and Jinja among other things, released a library for logging which, in his opinion, is preferable to the logging package provided as part of Python since the 2.3 release. Now preferences are a very personal thing, and since Armin has made worthy software contributions to the Python community, there will no doubt be many people who trust his judgement and follow where he leads. In general, choice in software is good, and people should be free to make up their own minds about the software they want to use.&amp;nbsp;&amp;nbsp; However, in order to make an informed decision, people need &lt;i&gt;accurate&lt;/i&gt; information on which to base that decision. For example, I chose to use &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;argparse&lt;/span&gt; over the stdlib's &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;optparse&lt;/span&gt; even before &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;argparse&lt;/span&gt; landed in the stdlib; in making that choice, I looked at Steven Bethard's rationale as described &lt;a href="http://argparse.googlecode.com/svn/trunk/doc/argparse-vs-optparse.html"&gt;here&lt;/a&gt; and was fortunate to have the time to be able to examine and evaluate each of his points for myself.&lt;br /&gt;
&lt;br /&gt;
In the case of choosing whether to use Python logging or something else, people may or may not have the time to investigate in any depth the relative merits of the alternatives on offer. Armin has seemingly made it easier for busy people by including, in his library's documentation, reasons why you should use his offering in preference to the stdlib's logging package, and what he perceives to be problems with the code in the stdlib. This is what Steven did for &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;argparse&lt;/span&gt;, but in the case of logging Armin has made a number of statements which are inaccurate or misleading. No doubt he has not meant to &lt;i&gt;deliberately&lt;/i&gt; mislead anyone, but some of the things he has said have led me to conclude that perhaps Armin doesn't really understand some of the aspects of how stdlib logging works, and how you can use it in Web applications, desktop applications, casual utility scripts, and of course libraries which support all of these application development constituencies. And if Armin - a clearly talented developer - is making some egregious mistakes in his thinking about how stdlib logging works and what you can or can't do with it, then it's possible that the lack of understanding which he demonstrates is more widespread; as maintainer of the stdlib logging package, I feel there's a need to do something to rectify this. Hence, this post.&lt;br /&gt;
&lt;br /&gt;
Isn't that what the stdlib documentation is for? Of course, at least partly, and while the stdlib logging documentation undergoes continual additions and revisions (which hopefully can be viewed as continuous improvement), it is a difficult medium in which to address certain points. Of necessity, the stdlib documentation for most modules is presented largely in a reference style, with only limited examples which might serve as tutorials. And even for sections where much effort has been expended in presenting using a tutorial style, there is always room for expositions which do not fit easily in the stdlib documentation: for example, a "cookbook" style presentation which tries to show how to solve common and less common problems which arise in considering how best to use a package. And then, who reads documentation, anyway? ;-)&lt;br /&gt;
&lt;br /&gt;
Of course no software is perfect, nor can any design or implementation hope to please all of its users all of the time. I have always viewed stdlib logging as capable of being improved, not because there was anything especially wrong with it to start with, but rather that through the philosophy of continuous improvement you have the best chance of providing something which remains relevant and useful to as many users as possible. That, it seems to me, is a core Python philosophy, enshrined in the procedures, standards and practices of the PEP process.&lt;br /&gt;
&lt;br /&gt;
You might be thinking that this post is a bit of an over-reaction on my part. Perhaps, but as well as the concern I expressed about there being a lack of understanding out there about how logging works, I have another concern. This is based on a strong view that for certain types of functionality, too much choice is not a good thing. For example, imagine if there were several competing implementations of regular expressions in Python, or implementations of pickling functionality, in common use. Some library developers would use one particular version, others a different one. So an application developer using several such libraries would not only have to pull in additional dependencies but also spend some time and thought on how to get the different libraries to work together. And so it is with logging, in my view; it's an infrastructure function and a lot of libraries out there already use it. Of course if it fell short of meeting developer requirements, then it should not be immune from being supplanted by a better alternative (as with &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;optparse&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;argparse&lt;/span&gt;). However, I believe that stdlib logging still remains fit for purpose, and am willing to stand up and say so when the criticisms made of it are without merit or overstated.&lt;br /&gt;
&lt;br /&gt;
So, let's review some of the statements in Armin's documentation and presentations which merit closer examination. It's a long list, hence the tl;dr at the top of this post, but there are even more points that I could have made if not for the lack of time :-(&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="comparison" style="width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Statements&lt;/th&gt;
&lt;th&gt;What they imply ("Myths")&lt;/th&gt;
&lt;th&gt;How it really is ("Facts")&lt;/th&gt;
&lt;th&gt;Conclusions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the opening paragraph in the "Why you should use Logbook" section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="stmt"&gt;Despite the existence of stdlib logging, you should use Logbook.&lt;/td&gt;
&lt;td class="imply"&gt;Logbook is better than stdlib logging now, and is ready to be used.&lt;/td&gt;
&lt;td class="facts"&gt;As stated in the same paragraph, Logbook is in the alpha stage of development and should be treated as a developer preview, and only supports Python 2.6 and 2.7.&lt;/td&gt;
&lt;td class="concl"&gt;Logbook is at present suitable for evaluation use only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the "Advantages over Logging" section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If properly configured, Logbook’s logging calls will be very cheap and provide a great performance improvement over the standard library’s logging module. While we are not there yet, there will be some performance improvements in the upcoming versions when we implement certain critical code paths in C.&lt;/td&gt;
&lt;td&gt;stdlib logging is slow.&lt;/td&gt;
&lt;td&gt;You can evaluate these test scripts yourself: &lt;a href="http://gist.github.com/586624"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;time_logging.py&lt;/span&gt;&lt;/a&gt; and &lt;a href="http://gist.github.com/586626"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;time_logbook.py&lt;/span&gt;&lt;/a&gt;. In the output of these scripts, you need to look at the "log_simple" line on each for a meaningful comparison. It's not a scientific benchmark, but on my machine, with these scripts, &lt;a href="http://gist.github.com/586637"&gt;the Logbook code runs around 30% slower than the stdlib code&lt;/a&gt;.&lt;/td&gt;
&lt;td&gt;Logbook is at present a fair bit slower than stdlib logging, so it is somewhat premature to boast about "great performance improvements".&lt;br /&gt;
While it's possible for Logbook to improve performance by writing performance-critical code in C, it's also possible for stdlib code to improve using the same approach. (Note, however, that building C extensions of third-party libraries from source under Windows can be a pain).&lt;br /&gt;
Anyway, according to the numbers shown by these test scripts, logging calls take of the order of &lt;i&gt;tens of microseconds&lt;/i&gt; - not a real concern in most scenarios.&lt;br /&gt;
If you are having performance problems which you think are due to logging overhead, the recommendation is to profile and see where the bottlenecks really are. If they are found to be in logging, please post your findings on comp.lang.python or bugs.python.org if you think that's appropriate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logbook also supports the ability to inject additional information for all logging calls happening in a specific thread. For example, this makes it possible for a web application to add request-specific information to each log record such as remote address, request URL, HTTP method and more.&lt;/td&gt;
&lt;td&gt;stdlib logging can't do this.&lt;/td&gt;
&lt;td&gt;I have already explained to Armin how to do this - see &lt;a href="http://plumberjack.blogspot.com/2010/09/configuring-logging-for-web.html"&gt;this post&lt;/a&gt;.&lt;/td&gt;
&lt;td&gt;stdlib logging CAN do this fairly easily.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Logbook system is (besides the stack) stateless and unit testing it is very simple. If context managers are used, it is impossible to corrupt the stack, so each test can easily hook in custom log handlers.&lt;/td&gt;
&lt;td&gt;There's something inherently good about a shared stack, but having a shared dict is somehow worse. Unit testing with stdlib logging is not simple. Unit tests with stdlib logging can't hook in custom handlers.&lt;/td&gt;
&lt;td&gt;There's no convincing argument that the type of shared state (execution context) offered by Logbook is better than how stdlib works. You just have to take it on trust. Unit testing with stdlib logging is not hard, since you can add/remove handlers to/from loggers, close handlers in tearDown which you opened in setUp, and disable loggers you don't need. Unit tests with stdlib logging CAN hook in any handlers you want; in fact, Python's own regression tests use unittest to do testing of the logging package itself.&lt;/td&gt;
&lt;td&gt;From the evidence presented, there's no proof of any actual functional advantage of Logbook over logging. You just have to take it on trust.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the "Design Principles" section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logbook [...] supports paradigms we think are more suitable for modern applications than the traditional Java inspired logging system that can also be found in the Python standard library and many more programming languages.&lt;/td&gt;
&lt;td&gt;Having a design that was inspired by a Java library is somehow bad, just because of Java. Tha fact that it appears in many more programming languages damns it even further.&lt;/td&gt;
&lt;td&gt;Although the Java log4j system was an influence on stdlib logging, this is because it had some good abstract ideas, well worth using. Though we have a lot of very clever people in the Python community, not every clever person is a Pythonista; it's very parochial to think that good ideas can only come from the Python community. The ideas that emerged in log4j and other packages are the ideas of "what happened? where did it happen? how important is it? who wants to know?" and if you think about it, these ideas are hardly Java-specific. In fact, they are pretty central to the problem domain addressed by logging. So - "what happened?" is the details of the logging call, "where did it happen?" is the logger name, "how important is it?" is the level, and "who wants to know?" is the handler. Hardly that complicated, and pretty much a minimum requirement for any logging package that aspires to the name.&lt;br /&gt;
Also, anyone who bothers to look at log4j in detail will see that Python logging is not a mindless translation - it's very Pythonic. Beyond the basic abstractions of "What? Where? How Important? Who Needs To Know?", there's no real correspondence between the Java artefacts and the Python ones. Using David A. Wheeler's SLOCCount, log4j 1.2.15 = 168 source files, around 16K SLOC; Python 2.6 logging = 3 source files, &amp;lt;  1.5K SLOC. Almost every class in stdlib logging maps to a core concept of logging, other than the handler classes, which are geared towards specific types of audience for logging messages.&lt;/td&gt;
&lt;td&gt;There's no indication that a Java influence has been detrimental to stdlib logging. All of the core concepts from stdlib logging are also present in Logbook: loggers ("where?"), levels ("how important?"), handlers ("who wants to know?") and the details passed to logging calls ("what happened?"). So if stdlib logging "suffers" from Java influences, so equally does Logbook.&lt;br /&gt;
It's also worth bearing in mind that Python logging works in Python 2.3 -&amp;gt; 2.7 and 3.0 -&amp;gt; 3.2, without the need to depend on specific features present only in the more recent versions of Python. And while a case can be made for certain programming styles like the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;with&lt;/span&gt; statement to have better support in stdlib logging, these are by no means essential to the basic business of logging; in any case support for new features can always be added as and when it's needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logbook is unique in that it has the concept of logging channels but that it does not keep a global registry of them. In the standard library’s logging module a logger is attached to a tree of loggers that are stored in the logging module itself as global state.&lt;/td&gt;
&lt;td&gt;The shared state embodied in Logbook's process or thread context is somehow good, but the shared tree of loggers stored as "global" state in stdlib logging is somehow bad.&lt;/td&gt;
&lt;td&gt;One reason for the shared state in stdlib logging is that application developers often need explicit access to loggers used by libraries, in order to control the level of verbosity of logging from those libraries, in the context of the application being developed. This verbosity varies over the life of an application - for example, when a bug is discovered in an application in production, it's common to selectively turn the verbosity of different library loggers up and down, while diagnosing the problem. You can't do that when the logger used by a library is hidden completely from a user of that library.&lt;br /&gt;
This is a fundamental point which may not bite developers working in limited scenarios, but I've had reason to rely on this functionality time and again. For example: I sometimes need to turn the verbosity of SQLAlchemy's logging so that I can examine SQL statements being generated from the ORM. If I couldn't access SQLAlchemy's top-level logger, using the name "sqlalchemy" and the fact that because of the "global" registry, the SQLAlchemy code and my code are both referring to the same object when they say logging.getLogger('sqlalchemy')), I couldn't turn this verbosity up and down, as and when needed.&lt;br /&gt;
Strictly speaking, the tree of loggers in stdlib logging does not need to be global. As a look at logging's source code will show, the tree of loggers is held as an instance variable of an instance of a Manager class. A binding to this Manager instance is currently stored in the Logger class, and through this binding you can say that the tree of loggers is global. This state of affairs has been the situation since the first release of logging in the stdlib.
&lt;br /&gt;
It would be well within the bounds of possibility to either expand the Manager class to be a context manager, or derive a context manager class from it. If this was done, then the tree of loggers need not be global: it would just be an attribute of the Manager instance, and there could be more than one Manager (or derived context manager) instance in a Python process. This improved context manager may well appear in the future, but at present there has not been a strong enough case made for the need for doing this. If such a case is made (for example, by someone generating a PEP or proto-PEP making the justification) then providing such a feature can be considerered, and it would not be a major undertaking (but not a trivial one, either).&lt;/td&gt;
&lt;td&gt;There is nothing wrong with logging's design just because it uses a shared registry of loggers. In fact, the shared registry confers some positive, practical benefits in real-life use cases.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In logbook a logger is just an opaque object [...]  the lifetime and availability of that object is controlled by the person creating that logger. The registry is necessary for the logging library to give the user the ability to configure these loggers.&lt;/td&gt;
&lt;td&gt;It's better for a library developer to keep their loggers completely encapsulated in the library, so application developers using that library have no access to it.&lt;/td&gt;
&lt;td&gt;The need to access the loggers in libraries comes up again and again in practice, particularly in production environments and long-running service processes where you can't just fire up a debugger, when a problem arises, to see what's causing it. With stdlib logging, features exist to turn the verbosity of library logging up and down, for different libraries at different times, without needing to stop the long-running processes.&lt;/td&gt;
&lt;td&gt;The stated advantage of Logbook over stdlib logging seems illusory. The logger registry actually solves practical problems in real-world environments where multiple libraries from multiple vendors are integrated into an application. It's not clear, from Logbook's documentation, whether you can tune the verbosity of logging in a library component which utilised Logbook for its logging functionality.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logbook has a completely different concept of dispatching from loggers to the actual handlers which removes the requirement and usefulness of such a registry. The advantage of the logbook system is that it’s a cheap operation to create a logger and that a logger can easily be garbage collected to remove all traces of it.&lt;/td&gt;
&lt;td&gt;There's no utility in a shared registry between different components of an application. Creating a logger in stdlib logging is expensive. It's important to be able to remove all traces of a logger that you created.&lt;/td&gt;
&lt;td&gt;The preceding discussions try to show why a shared registry is useful. There's no example given in the Logbook documentation of how you might (as an application developer) tune the logging verbosity of a component you use. It's not clear that this is even possible in Logbook.
&lt;br /&gt;
Creating loggers in the stdlib is not particularly expensive, and is a one-off operation; so amortized over many uses, the creation should actually be cheaper in the stdlib.
&lt;br /&gt;
It's true that loggers can't be garbage collected to remove all traces of it, but not clear why that is really important, as the memory used by loggers is not significant in normal usage.&lt;/td&gt;
&lt;td&gt;stdlib logging is designed the way it is for good reasons, not because its designer lacks imagination. The stated disadvantages of stdlib logging over Logbook are not proven, just stated as if they were.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logbook moves the burden of delivering a log record from the log channel’s attached log to an independent entity that looks at the context of the execution to figure out where to deliver it.&lt;/td&gt;
&lt;td&gt;The loggers in stdlib logging are suffering from some kind of burden when delivering log records.&lt;/td&gt;
&lt;td&gt;In both stdlib logging and Logbook, records are delivered using handlers. Loggers become a way for an application developer to finely control the logging verbosity of their application, so that they are in control rather than library developers who, of necessity, cannot anticipate every circumstance about how their libraries are used and deployed.&lt;/td&gt;
&lt;td&gt;The context of execution is insufficient to determine how records generated in libraries should be delivered. In practice, you need a combination of the execution context and the wishes of the application developer regarding how library logging is to be configured.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the "Context sensitive handler stack" section&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let’s take a GUI application [which] might fail or log messages. The typical default behaviour here would be to log into a logfile. Fair enough, that’s how these applications work. But what’s the point in logging if not even a single warning happened? The traditional solution with the logging library from Python is to set the level high (like ERROR or WARNING) and log into a file. When things break, you have a look at the file and hope it contains enough information. When you are in full control [...] with a stack based system like Logbook has, there is a lot more you can do. For example you could immediately after your application boots up instanciate a FingersCrossedHandler. This handler buffers all log records in memory and does not emit them at all. What’s the point? That handler activates when a certain threshold is reached. For example, when the first warning occurs you can write the buffered messages as well as the warning that just happened into a logfile and continue logging from that point. Because there is no point in logging when you will never look at that file anyways.&lt;/td&gt;
&lt;td&gt;This kind of functionality is only possible with a context sensitive handler stack, which only Logbook has. You can't do this with stdlib logging.&lt;/td&gt;
&lt;td&gt;You can do exactly this with stdlib logging, where from the earliest release there's been a &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;MemoryHandler&lt;/span&gt; class which buffers up records in memory until a message with a specified threshold level is seen, when all the messages buffered so far are then forwarded to a target handler for processing.&lt;/td&gt;
&lt;td&gt;The supposed advantage of a context-sensitive handler stack in this scenario isn't any particular advantage at all.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;But that alone is not the killer feature of a stack. In a GUI application there is the point where we are still initializing the windowing system. So a file is the best place to log messages. But once we have the GUI initialized, it would be very helpful to show error messages to a user in a console window or a dialog. So what we can do is to initialize at that point a new handler that logs into a dialog. When then a long running tasks in the GUI starts we can move that into a separate thread and intercept all the log calls for that thread into a separate window until the task succeeded.&lt;/td&gt;
&lt;td&gt;This is somehow a killer feature of a stack, which you can't do any other way.&lt;/td&gt;
&lt;td&gt;You can do this kind of processing perfectly easily with stdlib logging. There are examples of dialog- or window-based handlers (for Qt, say) in answers to questions on Stack Overflow. You can combine the provided stdlib logging handlers such as MemoryHandler and FileHandler together with your own window-specific handlers (dependent on which windowing system you are using) to achieve the sort of effect described.&lt;/td&gt;
&lt;td&gt;Another supposed advantage of a context sensitive handler stack, which isn't.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In Armin's &lt;a href="http://www.scribd.com/doc/37071444/Logbook"&gt;Logbook presentation&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the "Why not logging?" slide&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stdlib logging is not really suited for Web applications.&lt;/td&gt;
&lt;td&gt;Don't even bother trying to use stdlib logging with Web applications, it's not worth it.&lt;/td&gt;
&lt;td&gt;It's perfectly possible to use stdlib logging with Web application, as I showed Armin how to meet his requirements, in &lt;a href="http://plumberjack.blogspot.com/2010/09/configuring-logging-for-web.html"&gt;this post&lt;/a&gt;.&lt;/td&gt;
&lt;td&gt;This statement is just plain wrong and illustrates a lack of understanding of how to use the functionality of stdlib logging, rather than any lack of functionality in stdlib logging.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging's central registry of loggers makes unittesting a pain.&lt;/td&gt;
&lt;td&gt;It's too much trouble to use stdlib logging in your unittests.&lt;/td&gt;
&lt;td&gt;It doesn't need to be a pain. A future post on this blog will describe scenarios showing how to use logging in your unit tests.&lt;br /&gt;
It's probably possible to provide additional utility functions in stdlib logging to make some aspects of unit testing easier, but there are no real showstoppers; if Armin describes his problem scenario in as much detail as he described his Web application configuration problem, I will (hopefully) be able to suggest some ways of easing his pain.&lt;/td&gt;
&lt;td&gt;Unless specific details are given as to why unit testing with stdlib logging is considered to be a pain (i.e. what you'd like to do but what you can't do) then this statement must be taken with a pinch of salt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The same registry also causes issues when libraries want to start logging&lt;/td&gt;
&lt;td&gt;The registry is central to the issues caused by libraries doing logging&lt;/td&gt;
&lt;td&gt;There are documented things that library authors need to do when setting up logging for libraries. If library developers do not follow the recommendations then this might in fact cause problems, but that has nothing to do with the existence of a central registry (or at least, the connection has not been demonstrated).&lt;/td&gt;
&lt;td&gt;There's no evidence that the existence of a central registry is somehow responsible for how third-party libraries misuse logging.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You can't delete loggers once created.&lt;/td&gt;
&lt;td&gt;It's important that you be able to delete loggers once created.&lt;/td&gt;
&lt;td&gt;The reason why loggers are not deleted once created is that multiple threads (other than the one that wants to delete the logger) can have references to the logger. This is facilitated by having a central registry, but even in a system without a central registry, loggers could be passed between threads. In that scenario, you can't actually guarantee to delete the logger since there could be references to it in another thread.&lt;br /&gt;
It's not demonstrated that it's important to expunge all loggers completely from memory. The number of logger instances is related to the number of logical "areas" in an application - the idea of "where" in a component a particular event occurred. This is not so large that the amount of memory used by loggers becomes significant. And stdlib logging allows loggers to be disabled, which means that they cease to perform any logging (until re-enabled) and are as good as gone.&lt;/td&gt;
&lt;td&gt;It's true that you can't delete loggers, but they can be easily disabled; they do not, under normal usage patterns, occupy large amounts of memory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="secthead"&gt;&lt;td colspan="4"&gt;In the "Why does nobody like logging?" slide&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why does nobody like logging?&lt;/td&gt;
&lt;td&gt;Nobody likes logging.&lt;/td&gt;
&lt;td&gt;This is an opinion, not a fact. There are many people who have expressed their thanks to me for the existence of the logging package and feedback saying that it has been very useful to them.&lt;br /&gt;
Even Armin has told me that he liked logging "a lot", which is hard to reconcile with what he's now saying - since logging's basic design has been the same from the outset, and it seems to meet his functional needs even if it doesn't press his aesthetic hot buttons.&lt;/td&gt;
&lt;td&gt;There's no basis in fact for this statement. It's marketing FUD.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The default configuration of stdlib logging for applications is bad.&lt;/td&gt;
&lt;td&gt;The statement speaks for itself.&lt;/td&gt;
&lt;td&gt;On the contrary, the default configuration for applications is consistent with the principle of least surprise and the Zen of Python's "explicit is better than implicit". In common with the Unix philosophy, software should not be excessively verbose except when it needs to be, or is asked to be. By default, stdlib logging will only output messages of severity WARNING or higher. (It's easy to change this default.)
&lt;br /&gt;
In fact, in the default configuration of logbook 0.2, every DEBUG logging call in every library which uses Logbook in your application will print debug messages to stderr when your application runs, even if you specify that you want to see only messages of severity WARNING or greater. Perhaps you won't care if your application is a GUI application or a Web application, since you won't have a console, and perhaps logbook is expressly designed only for these types of application. For a console application, it's very likely you will get Too Much Information. Here is &lt;a href="http://gist.github.com/586656"&gt;an example&lt;/a&gt;.&lt;/td&gt;
&lt;td&gt;The default configuration of stdlib logging is quite sane; there's nothing wrong with it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Useless default configuration for libraries&lt;/td&gt;
&lt;td&gt;The statement speaks for itself.&lt;/td&gt;
&lt;td&gt;Library developers are supposed to add a NullHandler to their top-level logger and to the level and propagation flag as needed, but to not add any other handlers to their logging configuration because that configuration is the prerogative of the application developer. However, it's not possible to prevent library authors from breaking these rules.&lt;/td&gt;
&lt;td&gt;By default, libraries aren't supposed to handle messages - only to log them - and the application developer is expected to configure any logging (though they don't have to; if they don't, no logging output should be generated). This is consistent with the principle of least surprise and Zen of Python point mentioned earlier.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who sets up the logging config? Libraries sometimes call basicConfig.&lt;/td&gt;
&lt;td&gt;If library developers don't follow the guidelines set down for logging from libraries, that's somehow the fault of stdlib logging.&lt;/td&gt;
&lt;td&gt;There is documentation about how to configure logging in libraries, and while this may be lacking in some way, it's not immediately clear what that lack is.&lt;/td&gt;
&lt;td&gt;Perhaps more or better documentation is needed, but that won't eliminate completely the possibility that a library developer will misguidedly add handlers to the logging configuration, say by calling basicConfig().&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logging.warn() and friends are not thread-safe.&lt;/td&gt;
&lt;td&gt;Stay away from stdlib logging if you want to use threads.&lt;/td&gt;
&lt;td&gt;If logging is correctly configured in an application, there should be no untoward behaviour by stdlib logging. In versions of Python earlier than 2.7.1 and 3.2, the code in basicConfig() omitted acquiring and releasing an internal lock, and you could demonstrate incorrect behaviour in stdlib logging by calling basicConfig() directly or indirectly from multiple threads; this has been fixed in 2.7.1 and 3.2, but nevertheless, it is wrong to do any logging from multiple threads before configuring handlers (if you want any logging output, that is) - because if no handlers have been configured before threads start running, any messages they log could be lost (because there are no handlers to deliver them). &lt;br /&gt;
So, good practice would mean that this problem shouldn't occur in practice, and even if it does, the worst consequence is that handlers are added multiple times, resulting in multiple messages output for the same event.&lt;/td&gt;
&lt;td&gt;It's disingenuous to say that these functions are not thread-safe. Although the statement is technically correct, the unexpected behaviour only occurs if logging is not configured before multiple threads are started, and more than one of these threads calls basicConfig() either directly or via logging.warn() and friends.&lt;br /&gt;
Even then, the behaviour occurs only rarely, due to thread timing uncertainties.&lt;br /&gt;
In any case, the incorrect behaviour will not occur in Python versions &amp;gt;= 2.7.1 or &amp;gt;= 3.2, because the requisite lock acquisition/release code has now been added.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-233393633116721311?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/233393633116721311/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/python-logging-functionality-facts-vs.html#comment-form' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/233393633116721311'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/233393633116721311'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/python-logging-functionality-facts-vs.html' title='Python Logging Functionality - Facts vs. Myths'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-8174860998111074494</id><published>2010-09-17T09:10:00.000-07:00</published><updated>2010-09-18T10:34:32.571-07:00</updated><title type='text'>Configuring logging for Web applications</title><content type='html'>&lt;b&gt;tl;dr&lt;/b&gt;: You can &lt;i&gt;easily&lt;/i&gt; configure your web applications 
with Python logging, so that logged messages can go into 
web-application-specific log files (or other destinations) even for 
multiple web applications in the same Python process, and have those 
logs contain request-specific information such as remote user IP 
address, request method and others. Demonstration scripts are &lt;a href="http://gist.github.com/582257"&gt;here&lt;/a&gt; and &lt;a href="http://gist.github.com/582259"&gt;here&lt;/a&gt;, copy them to the same initially empty folder and run &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;webapptest.py&lt;/span&gt; in that folder, and then look at &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app*.log&lt;/span&gt;. Look at the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;InjectingFilter&lt;/span&gt; class - that's the core of the solution. Read on for more information.&lt;br /&gt;
&lt;br /&gt;
In a &lt;a href="http://www.reddit.com/r/Python/comments/ddkal/django_vs_web2py_what_do_you_use_and_why/c0zlgpy"&gt;recent post&lt;/a&gt; on Reddit, Armin Ronacher, developer of Werkzeug and Jinja among other things, had this to say:&lt;br /&gt;
&lt;blockquote&gt;
I don't know a setup of logging that would work out for me. But maybe you do and could share. Say I am a library called by anyone and use three or five loggers without any handlers. Now in the same Python process are two different web applications, each independently but using the same library. How can each of these applications have their own log where all logging messages from that library are redirected to the correct one of these application's log files (the correct one is the one where all calls were issued by the same handling thread) including additional information such as current IP and HTTP request method. I tried, I failed.&lt;/blockquote&gt;
Now, Armin has recently been working on his own logging library, and saying at conferences and in other public fora that he's done this mainly because he thinks Python logging is not really suited to Web applications. This, despite the fact that it's used in Google App Engine, Pylons, Tornado and other Web application platforms, by the many developers that use them. Now Armin can make a little arm-waving FUD go a long way, and I was very glad to have an actual problem statement from him that I could try to nail with a solution. I'm pleased to say it only took around ten minutes or so of work to come up with something which seems to solve his problem for him. His response? "This is actually quite neat ..." ;-)&lt;br /&gt;
&lt;br /&gt;
To illustrate the solution, I first wrote a simple module which represents the library. This demonstration example only uses one logger, but it could use three or five loggers and the solution would be the same. Here is the library module, christened &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;webapplib.py&lt;/span&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;useful&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello from webapplib!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;useful()&lt;/span&gt; function represents a call into the library from a Web application; this call will be made during request processing. The &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;time.sleep()&lt;/span&gt; call is there to simulate real activity, so that multiple threads will definitely be running at the same time processing requests, for a more realistic scenario.&lt;br /&gt;
&lt;br /&gt;
The next step was to construct a test harness which simulates requests and web applications. First, the request:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;

&lt;span class="n"&gt;REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.20'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.20'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.21'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.21'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.22'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'192.168.2.22'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The dummy &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Request&lt;/span&gt; class just has HTTP method and IP, as example attributes which need to be put into the logs. The &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;REQUESTS&lt;/span&gt; array is just a list of dummy requests which will be sent, during the test run, to the dummy web applications.&lt;br /&gt;
&lt;br /&gt;
Next, we need to consider how to simulate the Web applications. Let's start with the following skeleton snippet:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%(asctime)s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(threadName)-11s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(appName)s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(name)-9s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(ip)s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(method)-4s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing started'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;webapplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useful&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing finished'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;appName&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;method&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;ip&lt;/span&gt; in the format string are not part of the normal set of attributes which are part of logging: they are extra request-specific attributes which we want to inject into all log messages. In order for the format string to have the desired effect, attributes with those names will need to be added to the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;LogRecord&lt;/span&gt; for the event being logged, before formatting takes place; we'll come to that in a bit.&lt;br /&gt;
&lt;br /&gt;
The &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;WebApp&lt;/span&gt; class, representing a dummy web application, is fairly straightforward: we initialize a &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;FileHandler&lt;/span&gt; for that application in the constructor, based on its name, set its formatter, and add it to the root logger.&lt;br /&gt;
&lt;br /&gt;
We maintain a counter of requests processed by each &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;WebApp&lt;/span&gt; instance. This counter is incremented in &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;process_request&lt;/span&gt;, which we will call from our top-level test harness to simulate processing a request. This method logs two DEBUG messages, and calls into the common &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;webapplib&lt;/span&gt; library's &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;useful()&lt;/span&gt; method, which will also log a DEBUG message. So in total, each processed request should result in 3 lines in the log.&lt;br /&gt;
&lt;br /&gt;
Before looking at the specifics of the solution to the problem, let's see what the top-level test harness looks like:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;app1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'app1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'app2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;app1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c"&gt;#Add a common handler which will capture all events&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'app.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;#while True:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUESTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
            &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;    
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; processed &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; requests'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
So the test harness creates two dummy Web applications called &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app1&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app2&lt;/span&gt;, and sends a thousand requests (chosen at random from our array of pre-constructed requests) to an app which is also chosen at random. A &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;FileHandler&lt;/span&gt; for 'app.log' is created and added to the root logger, which will capture all logging (i.e. across all Web applications). Each request is processed on a separate thread.&lt;br /&gt;
&lt;br /&gt;
So far, we've described the simulation artefacts and the overall structure of the test harness. Now let's look at the core of the solution. Let's review the problems we have to solve:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Each &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;LogRecord&lt;/span&gt; needs the relevant thread-specific information injected into it before formatting.&lt;/li&gt;
&lt;li&gt;The handler for each of the application-specific logs needs to filter out any record which is not intended for that log.&lt;/li&gt;
&lt;/ul&gt;
We can solve both of these by using an appropriate &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Filter&lt;/span&gt; subclass. We'll call it &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;InjectingFilter&lt;/span&gt;, because it injects data into the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;LogRecord&lt;/span&gt; as well as acting as a filter. But how will it work? We'll use a &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;threading.local&lt;/span&gt; object to hold thread-specific state which is needed by the filter. Here's the first cut of what &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;InjectingFilter&lt;/span&gt; looks like:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;

&lt;span class="n"&gt;tlocal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InjectingFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
This shows just the injection logic of the filter, and not any filtering logic. The thread-local object &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;tlocal&lt;/span&gt; will hold a reference to the request currently being processed, which the filter uses to inject HTTP method and IP address into the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;LogRecord&lt;/span&gt;. This request needs to be set into the thread-local in &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;WebApp.process_request&lt;/span&gt;, which now looks like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing started'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;webapplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useful&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing finished'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
So far, so good. Next, we need to implement the filtering part: this is done by attaching, to each app-specific handler, an app-specific filter. In order to operate effectively, the  filter needs to know whether the current thread belongs to its Web application (in which case, the record is accepted for processing) or not (in which case, the record is rejected). One easy way of doing this is to ensure that the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;WebApp&lt;/span&gt; maintains a set of names of the threads it's currently using, and the filter can just check if the current thread belongs to its app's set. So we need to keep a reference to the app in the filter, and this also allows the injection of the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;appName&lt;/span&gt; attribute into the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;LogRecord&lt;/span&gt;. So the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;WebApp&lt;/span&gt; class will now look like this (simple implementation):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InjectingFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
        &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;tname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_requests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing started'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;webapplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useful&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Request processing finished'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
and the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;InjectingFilter&lt;/span&gt; class will look like this&amp;nbsp; (simple implementation):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InjectingFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tlocal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appName&lt;/span&gt;
        &lt;span class="n"&gt;tname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
You can pick up the whole script as &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;webapptest.py&lt;/span&gt; from &lt;a href="http://gist.github.com/582259"&gt;here&lt;/a&gt;, and use it with the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;webapplib.py&lt;/span&gt; script shown as the first code in this post. What happens if we run it?&lt;br /&gt;
&lt;pre&gt;vinay@zeta-lucid:~/projects/scratch$ python webapptest.py
app1 processed 506 requests
app2 processed 494 requests
vinay@zeta-lucid:~/projects/scratch$ wc -l app*.log
  1518 app1.log
  1482 app2.log
  3000 app.log
  6000 total &lt;/pre&gt;
So the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log&lt;/span&gt; file contains all the log messages - 3 per request, for a total of 3000 - and the lines in &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app1.log&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app2.log&lt;/span&gt; add up to 3000 too, which looks promising. Plus the individual line counts for these files are 3 x the number of requests processed, so that's also consistent. Let's look at the first few lines of each file:&lt;br /&gt;
&lt;pre&gt;vinay@zeta-lucid:~/projects/scratch$ head app.log app1.log app2.log
==&amp;gt; app.log &amp;lt;==
2010-09-17 16:11:48,226 Thread-1&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.20 GET&amp;nbsp; Request processing started
2010-09-17 16:11:48,227 Thread-1&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.20 GET&amp;nbsp; Hello from webapplib!
2010-09-17 16:11:48,228 Thread-2&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.21 POST Request processing started
2010-09-17 16:11:48,229 Thread-2&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.21 POST Hello from webapplib!
2010-09-17 16:11:48,230 Thread-3&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 POST Request processing started
2010-09-17 16:11:48,230 Thread-3&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.21 POST Hello from webapplib!
2010-09-17 16:11:48,231 Thread-4&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.22 POST Request processing started
2010-09-17 16:11:48,232 Thread-4&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.22 POST Hello from webapplib!
2010-09-17 16:11:48,233 Thread-5&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 GET&amp;nbsp; Request processing started
2010-09-17 16:11:48,233 Thread-5&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.21 GET&amp;nbsp; Hello from webapplib!

==&amp;gt; app1.log &amp;lt;==
2010-09-17 16:11:48,226 Thread-1&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.20 GET&amp;nbsp; Request processing started
2010-09-17 16:11:48,227 Thread-1&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.20 GET&amp;nbsp; Hello from webapplib!
2010-09-17 16:11:48,228 Thread-2&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.21 POST Request processing started
2010-09-17 16:11:48,229 Thread-2&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.21 POST Hello from webapplib!
2010-09-17 16:11:48,231 Thread-4&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.22 POST Request processing started
2010-09-17 16:11:48,232 Thread-4&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.22 POST Hello from webapplib!
2010-09-17 16:11:48,236 Thread-7&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.21 POST Request processing started
2010-09-17 16:11:48,236 Thread-7&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.21 POST Hello from webapplib!
2010-09-17 16:11:48,237 Thread-8&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 __main__&amp;nbsp; 192.168.2.22 POST Request processing started
2010-09-17 16:11:48,238 Thread-8&amp;nbsp;&amp;nbsp;&amp;nbsp; app1 webapplib 192.168.2.22 POST Hello from webapplib!

==&amp;gt; app2.log &amp;lt;==
2010-09-17 16:11:48,230 Thread-3&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 POST Request processing started
2010-09-17 16:11:48,230 Thread-3&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.21 POST Hello from webapplib!
2010-09-17 16:11:48,233 Thread-5&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 GET&amp;nbsp; Request processing started
2010-09-17 16:11:48,233 Thread-5&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.21 GET&amp;nbsp; Hello from webapplib!
2010-09-17 16:11:48,234 Thread-6&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.22 GET&amp;nbsp; Request processing started
2010-09-17 16:11:48,235 Thread-6&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.22 GET&amp;nbsp; Hello from webapplib!
2010-09-17 16:11:48,239 Thread-9&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.20 POST Request processing started
2010-09-17 16:11:48,239 Thread-9&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 webapplib 192.168.2.20 POST Hello from webapplib!
2010-09-17 16:11:48,241 Thread-3&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 POST Request processing finished
2010-09-17 16:11:48,244 Thread-5&amp;nbsp;&amp;nbsp;&amp;nbsp; app2 __main__&amp;nbsp; 192.168.2.21 GET&amp;nbsp; Request processing finished
&lt;/pre&gt;
So &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log&lt;/span&gt; contains interspersed messages from &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app1&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app2&lt;/span&gt;, but &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app1.log&lt;/span&gt; seems to contain just &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app1&lt;/span&gt; messages, and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app2.log&lt;/span&gt; seems to contain just &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app2&lt;/span&gt; messages. This is encouraging, and we can confirm with:&lt;br /&gt;
&lt;pre&gt;vinay@zeta-lucid:~/projects/scratch$ grep app1 app1.log | wc -l
1518
vinay@zeta-lucid:~/projects/scratch$ grep app2 app1.log | wc -l
0
vinay@zeta-lucid:~/projects/scratch$ grep app2 app2.log | wc -l
1482
vinay@zeta-lucid:~/projects/scratch$ grep app1 app2.log | wc -l
0&lt;/pre&gt;
The log lines all contain appName, request method and IP address, as required, Job done, it seems.&lt;br /&gt;
&lt;br /&gt;
Of course, this is a test scenario with dummy requests and web applications implemented simplistically, but the principle of the solution will apply equally to real web application server environments.&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-8174860998111074494?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/8174860998111074494/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/configuring-logging-for-web.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8174860998111074494'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8174860998111074494'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/configuring-logging-for-web.html' title='Configuring logging for Web applications'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-8614090780157540538</id><published>2010-09-13T23:45:00.000-07:00</published><updated>2010-09-14T04:57:55.348-07:00</updated><title type='text'>QueueHandler and ZeroMQ support</title><content type='html'>A new handler has been added to logging for Python 3.2: &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;QueueHandler&lt;/span&gt;&amp;nbsp;. This allows you to send logging events to a queue of some description. For users of earlier versions of Python, here is the latest code from the Py3K branch:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="sd"&gt;"""&lt;/span&gt; 
&lt;span class="sd"&gt;    This handler sends events to a queue. Typically, it would be used together&lt;/span&gt; 
&lt;span class="sd"&gt;    with a multiprocessing Queue to centralise logging to file in one process&lt;/span&gt; 
&lt;span class="sd"&gt;    (in a multi-process application), so as to avoid file write contention&lt;/span&gt; 
&lt;span class="sd"&gt;    between processes.&lt;/span&gt; 
 
&lt;span class="sd"&gt;    This code is new in Python 3.2, but this class can be copy pasted into&lt;/span&gt; 
&lt;span class="sd"&gt;    user code for use with earlier Python versions.&lt;/span&gt; 
&lt;span class="sd"&gt;    """&lt;/span&gt; 
 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="sd"&gt;"""&lt;/span&gt; 
&lt;span class="sd"&gt;        Initialise an instance, using the passed queue.&lt;/span&gt; 
&lt;span class="sd"&gt;        """&lt;/span&gt; 
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; 
 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="sd"&gt;"""&lt;/span&gt; 
&lt;span class="sd"&gt;        Enqueue a record.&lt;/span&gt; 
 
&lt;span class="sd"&gt;        The base implementation uses put_nowait. You may want to override&lt;/span&gt; 
&lt;span class="sd"&gt;        this method if you want to use blocking, timeouts or custom queue&lt;/span&gt; 
&lt;span class="sd"&gt;        implementations.&lt;/span&gt; 
&lt;span class="sd"&gt;        """&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="sd"&gt;"""&lt;/span&gt; 
&lt;span class="sd"&gt;        Emit a record.&lt;/span&gt; 
 
&lt;span class="sd"&gt;        Writes the LogRecord to the queue, preparing it for pickling first.&lt;/span&gt; 
&lt;span class="sd"&gt;        """&lt;/span&gt; 
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="c"&gt;# The format operation gets traceback text into record.exc_text&lt;/span&gt; 
            &lt;span class="c"&gt;# (if there's exception data), and also puts the message into&lt;/span&gt; 
            &lt;span class="c"&gt;# record.message. We can then use this to replace the original&lt;/span&gt; 
            &lt;span class="c"&gt;# msg + args, as these might be unpickleable. We also zap the&lt;/span&gt; 
            &lt;span class="c"&gt;# exc_info attribute, as it's no longer needed and, if not None,&lt;/span&gt; 
            &lt;span class="c"&gt;# will typically not be pickleable.&lt;/span&gt; 
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; 
            &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; 
            &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exc_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; 
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
            &lt;span class="k"&gt;raise&lt;/span&gt; 
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
This code is perfectly usable in earlier Python versions, including 2.x - just copy and paste it into your own code. In addition to usage with queues from the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;queue&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;multiprocessing&lt;/span&gt;&amp;nbsp;modules (as described in &lt;a href="http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html"&gt;this earlier post&lt;/a&gt;), the QueueHandler makes it easy to support other queue-like objects, such as ZeroMQ sockets.&lt;br /&gt;
&lt;br /&gt;
In the example below, a PUBLISH socket is created separately and passed to the handler (as its ‘queue’):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;zmq&lt;/span&gt; &lt;span class="c"&gt;# using pyzmq, the Python binding for ZeroMQ&lt;/span&gt; 
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt; &lt;span class="c"&gt;# for serializing records portably&lt;/span&gt; 
 
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# or zmq.PUSH, or other suitable value&lt;/span&gt; 
&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'tcp://*:5556'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# or wherever&lt;/span&gt; 
 
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ZeroMQSocketHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZeroMQSocketHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
Of course there are other ways of organizing this, for example passing in the data needed by the handler to create the socket:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ZeroMQSocketHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socktype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
        &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socktype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="n"&gt;QueueHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
 
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(self):&lt;/span&gt; 
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
To test this out, put this together into a little test script (imports not shown, but you can get the working script &lt;a href="http://gist.github.com/578918"&gt;here&lt;/a&gt;):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; 
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Enter messages to send:'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZeroMQSocketHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'tcp://*:5556'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&amp;gt; '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
For the receiving end, you can use a simple script like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt; 
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pprint&lt;/span&gt; 
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;zmq&lt;/span&gt; 
 
&lt;span class="n"&gt;URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'tcp://localhost:5556'&lt;/span&gt; 
 
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; 
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Receiving on a SUB socket: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUBSCRIBE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'-'&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
        &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
 
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
And the output would be something like:
&lt;br /&gt;
&lt;pre&gt;Receiving on a SUB socket: tcp://localhost:5556
{u'args': None,
&amp;nbsp;u'created': 1284446528.9099669,
&amp;nbsp;u'exc_info': None,
&amp;nbsp;u'exc_text': None,
&amp;nbsp;u'filename': u'zmqlog.py',
&amp;nbsp;u'funcName': u'main',
&amp;nbsp;u'levelname': u'WARNING',
&amp;nbsp;u'levelno': 30,
&amp;nbsp;u'lineno': 78,
&amp;nbsp;u'message': u"It's easy to log to ZeroMQ!",
&amp;nbsp;u'module': u'zmqlog',
&amp;nbsp;u'msecs': 909.96694564819336,
&amp;nbsp;u'msg': u"It's easy to log to ZeroMQ!",
&amp;nbsp;u'name': u'root',
&amp;nbsp;u'pathname': u'zmqlog.py',
&amp;nbsp;u'process': 13647,
&amp;nbsp;u'processName': u'MainProcess',
&amp;nbsp;u'relativeCreated': 521204.14185523987,
&amp;nbsp;u'thread': -1215568192,
&amp;nbsp;u'threadName': u'MainThread'}
----------------------------------------
&lt;/pre&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-8614090780157540538?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/8614090780157540538/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/queuehandler-and-zeromq-support.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8614090780157540538'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8614090780157540538'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/queuehandler-and-zeromq-support.html' title='QueueHandler and ZeroMQ support'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-8739605627079941233</id><published>2010-09-13T04:46:00.000-07:00</published><updated>2010-09-13T04:46:47.601-07:00</updated><title type='text'>Using filters to add contextual information to logs</title><content type='html'>As an alternative to using LoggerAdapters to add contextual information to logs, you can also use Filters to achieve the same result. This approach has been documented with an example in the in-development Python documentation, &lt;a href="http://docs.python.org/dev/library/logging.html#using-filters-to-impart-contextual-information"&gt;here&lt;/a&gt;. This functionality has been available since the earliest version of Python logging (i.e. since 2.3) but since the advent of thread-locals in 2.4 it can be used to log thread-local information. This can be used, for example, in an HTTP request handler, to record request-specific information such as client IP address, HTTP method, path, query string etc. in your logs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-8739605627079941233?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/8739605627079941233/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-filters-to-add-contextual.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8739605627079941233'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8739605627079941233'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-filters-to-add-contextual.html' title='Using filters to add contextual information to logs'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-5984417199905766959</id><published>2010-09-07T03:07:00.000-07:00</published><updated>2010-09-07T13:34:13.814-07:00</updated><title type='text'>Using logging with multiprocessing</title><content type='html'>There can be a few gotchas when using logging with the multiprocessing module. For example, if you want to write rotated log files from your multi-process application, a naïve implementation might just configure a RotatingFileHandler directly. This could lead to multiple processes trying to write to the file concurrently, which will almost certainly lead to the log getting corrupted because of interleaved writes by different processes.&lt;br /&gt;
&lt;br /&gt;
Note that logging to the same rotated files from multiple &lt;i&gt;threads&lt;/i&gt; in a single-process application would be fine; the logging package uses threading locks to ensure that no log corruption occurs. There's no equivalent cross-platform synhronisation for processes in the stdlib, however; that's why you can get corruption with multi-process applications.&lt;br /&gt;
&lt;br /&gt;
To circumvent the problem scenario, you can use a multiprocessing Queue and a listener process which listens for logging events sent to the queue. When it sees these events, it pops them off the queue and processes them; as it's the only process which will write to files directly, there are no contention issues which lead to corruption. The other processes just need to configure a QueueHandler, which will send logging events via the queue to the listener process.&lt;br /&gt;
&lt;br /&gt;
The plan is to add QueueHandler to Python 3.2, but the implementation here is simple enough and should be copy-pastable into your own code for use with earlier Python versions.&lt;br /&gt;
&lt;br /&gt;
The script is fairly well annotated so I'll say no more.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="c"&gt;#!/usr/bin/env python&lt;/span&gt;
&lt;span class="c"&gt;# Copyright (C) 2010 Vinay Sajip. All Rights Reserved.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Permission to use, copy, modify, and distribute this software and its&lt;/span&gt;
&lt;span class="c"&gt;# documentation for any purpose and without fee is hereby granted,&lt;/span&gt;
&lt;span class="c"&gt;# provided that the above copyright notice appear in all copies and that&lt;/span&gt;
&lt;span class="c"&gt;# both that copyright notice and this permission notice appear in&lt;/span&gt;
&lt;span class="c"&gt;# supporting documentation, and that the name of Vinay Sajip&lt;/span&gt;
&lt;span class="c"&gt;# not be used in advertising or publicity pertaining to distribution&lt;/span&gt;
&lt;span class="c"&gt;# of the software without specific, written prior permission.&lt;/span&gt;
&lt;span class="c"&gt;# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING&lt;/span&gt;
&lt;span class="c"&gt;# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL&lt;/span&gt;
&lt;span class="c"&gt;# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR&lt;/span&gt;
&lt;span class="c"&gt;# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER&lt;/span&gt;
&lt;span class="c"&gt;# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT&lt;/span&gt;
&lt;span class="c"&gt;# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;An example script showing how to use logging with multiprocessing.&lt;/span&gt;

&lt;span class="sd"&gt;The basic strategy is to set up a listener process which can have any logging&lt;/span&gt;
&lt;span class="sd"&gt;configuration you want - in this example, writing to rotated log files. Because&lt;/span&gt;
&lt;span class="sd"&gt;only the listener process writes to the log files, you don't have file&lt;/span&gt;
&lt;span class="sd"&gt;corruption caused by multiple processes trying to write to the file.&lt;/span&gt;

&lt;span class="sd"&gt;The listener process is initialised with a queue, and waits for logging events&lt;/span&gt;
&lt;span class="sd"&gt;(LogRecords) to appear in the queue. When they do, they are processed according&lt;/span&gt;
&lt;span class="sd"&gt;to whatever logging configuration is in effect for the listener process.&lt;/span&gt;

&lt;span class="sd"&gt;Other processes can delegate all logging to the listener process. They can have&lt;/span&gt;
&lt;span class="sd"&gt;a much simpler logging configuration: just one handler, a QueueHandler, needs&lt;/span&gt;
&lt;span class="sd"&gt;to be added to the root logger. Other loggers in the configuration can be set&lt;/span&gt;
&lt;span class="sd"&gt;up with levels and filters to achieve the logging verbosity you need.&lt;/span&gt;

&lt;span class="sd"&gt;A QueueHandler processes events by sending them to the multiprocessing queue&lt;/span&gt;
&lt;span class="sd"&gt;that it's initialised with.&lt;/span&gt;

&lt;span class="sd"&gt;In this demo, there are some worker processes which just log some test messages&lt;/span&gt;
&lt;span class="sd"&gt;and then exit.&lt;/span&gt;

&lt;span class="sd"&gt;This script was tested on Ubuntu Jaunty and Windows 7.&lt;/span&gt;

&lt;span class="sd"&gt;Copyright (C) 2010 Vinay Sajip. All Rights Reserved.&lt;/span&gt;
&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="c"&gt;# You'll need these imports in your own code&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging.handlers&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;multiprocessing&lt;/span&gt;

&lt;span class="c"&gt;# Next two import lines for this demo only&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;    This is a logging handler which sends events to a multiprocessing queue.&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    The plan is to add it to Python 3.2, but this can be copy pasted into&lt;/span&gt;
&lt;span class="sd"&gt;    user code for use with earlier Python versions.&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Initialise an instance, using the passed queue.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
        
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;        Emit a record.&lt;/span&gt;

&lt;span class="sd"&gt;        Writes the LogRecord to the queue.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ei&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exc_info&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# just to get traceback text into record.exc_text&lt;/span&gt;
                &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exc_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c"&gt;# not needed any more&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Because you'll want to define the logging configurations for listener and workers, the&lt;/span&gt;
&lt;span class="c"&gt;# listener and worker process functions take a configurer parameter which is a callable&lt;/span&gt;
&lt;span class="c"&gt;# for configuring logging for that process. These functions are also passed the queue,&lt;/span&gt;
&lt;span class="c"&gt;# which they use for communication.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# In practice, you can configure the listener however you want, but note that in this&lt;/span&gt;
&lt;span class="c"&gt;# simple example, the listener does not apply level or filter logic to received records.&lt;/span&gt;
&lt;span class="c"&gt;# In practice, you would probably want to do ths logic in the worker processes, to avoid&lt;/span&gt;
&lt;span class="c"&gt;# sending events which would be filtered out between processes.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# The size of the rotated files is made small so you can see the results easily.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;listener_configurer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RotatingFileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/tmp/mptest.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%(asctime)s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(processName)-10s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(name)s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(levelname)-8s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# This is the listener process top-level loop: wait for logging events&lt;/span&gt;
&lt;span class="c"&gt;# (LogRecords)on the queue and handle them, quit when you get a None for a &lt;/span&gt;
&lt;span class="c"&gt;# LogRecord.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;listener_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c"&gt;# We send this as a sentinel to tell the listener to quit.&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# No level or filter logic applied - just do it!&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;traceback&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Whoops! Problem:'&lt;/span&gt;
            &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print_exc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Arrays used for random selections in this demo&lt;/span&gt;

&lt;span class="n"&gt;LEVELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRITICAL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;LOGGERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'a.b.c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'d.e.f'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;MESSAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'Random message #1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Random message #2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Random message #3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# The worker configuration is done at the start of the worker process run.&lt;/span&gt;
&lt;span class="c"&gt;# Note that on Windows you can't rely on fork semantics, so each process&lt;/span&gt;
&lt;span class="c"&gt;# will run the logging configuration code when it starts.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker_configurer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QueueHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# Just the one handler needed&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# send all messages, for demo; no other level or filter logic applied.&lt;/span&gt;

&lt;span class="c"&gt;# This is the worker process top-level loop, which just logs ten events with&lt;/span&gt;
&lt;span class="c"&gt;# random intervening delays before terminating.&lt;/span&gt;
&lt;span class="c"&gt;# The print messages are just so you know it's doing something!&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Worker started: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOGGERS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LEVELS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MESSAGES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Worker finished: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Here's where the demo gets orchestrated. Create the queue, create and start&lt;/span&gt;
&lt;span class="c"&gt;# the listener, create ten workers and start them, wait for them to finish,&lt;/span&gt;
&lt;span class="c"&gt;# then send a None to the queue to tell the listener to finish.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;listener_process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listener_configurer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;worker_process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker_configurer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-5984417199905766959?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/5984417199905766959/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5984417199905766959'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5984417199905766959'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html' title='Using logging with multiprocessing'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-5225039323223935177</id><published>2010-09-03T02:46:00.000-07:00</published><updated>2010-09-03T02:46:54.227-07:00</updated><title type='text'>Using a custom file naming scheme for rotated log files</title><content type='html'>When you use rotated log files with Python logging's built-in functionality, the rotated files are named by appending a number to the base log file name: thus, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log&lt;/span&gt; would give rise to log files &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log.1&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log.2&lt;/span&gt; etc.&lt;br /&gt;
&lt;br /&gt;
 Sometimes, you may not want this: for example, you may want to preserve the file extension so that you can take advantage of file associations (typically on Windows). You can implement a scheme where log file names take the form &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.log&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.1.log&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;app.2.log&lt;/span&gt; etc. by subclassing &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;RotatingFileHandler&lt;/span&gt; and overriding the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;doRollover()&lt;/span&gt; method. The exact code for this method varies slightly across different versions of Python, so I won't reproduce the whole method, but there's always an &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;if&lt;/span&gt; statement in the method which does the rotation. In your overridden method, you can use the following logic (in place of the default logic) to implement an extension-preserving rotation scheme, for example as follows:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backupCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backupCount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sfn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;%d%s&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dfn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;%d%s&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dfn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseFilename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-5225039323223935177?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/5225039323223935177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-custom-file-naming-scheme-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5225039323223935177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/5225039323223935177'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/09/using-custom-file-naming-scheme-for.html' title='Using a custom file naming scheme for rotated log files'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-4646716307974901404</id><published>2010-07-06T10:21:00.000-07:00</published><updated>2010-07-06T10:21:04.714-07:00</updated><title type='text'>Using a custom Formatter to deal with Unicode messages</title><content type='html'>Sometimes, you want to use Unicode in messages, and different logging handlers deal with Unicode in different ways. For example, FileHandler allows you to specify an encoding, which is then used to encode Unicode messages to bytes. In Python 2.x, SMTPHandler doesn't do any encoding, which can lead to UnicodeEncodeErrors being raised when smtplib writes the message to a socket.&lt;br /&gt;
&lt;br /&gt;
To avoid this, you can use a Formatter which encodes the message for you, as in the following example:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="hlcode"&gt;
&lt;div class="syntax"&gt;
&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;logging.handlers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EncodingFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datefmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datefmt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;unicode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMTPHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mailhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                      &lt;span class="n"&gt;fromaddr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'vms@test.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;toaddrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'test@test.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Logged Event'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EncodingFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'iso8859-1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;u'accentu&lt;/span&gt;&lt;span class="se"&gt;\u00e9&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&amp;nbsp;&lt;/pre&gt;
&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-4646716307974901404?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/4646716307974901404/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2010/07/using-custom-formatter-to-deal-with.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/4646716307974901404'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/4646716307974901404'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2010/07/using-custom-formatter-to-deal-with.html' title='Using a custom Formatter to deal with Unicode messages'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-892209330645689048</id><published>2009-10-17T00:33:00.000-07:00</published><updated>2009-10-29T09:05:24.781-07:00</updated><title type='text'>PEP for logging configuration using dictionaries</title><content type='html'>&lt;style type="text/css"&gt;
h1.internal {
  font-family: 'Trebuchet MS',Trebuchet,Verdana,Sans-Serif;
  color: #000;
  background: #F6F6F6;
  font-size: 190%;
  font-size-adjust: none;
  font-stretch: normal;
  padding: 0;
  letter-spacing: 0px;
}
&lt;/style&gt;
&lt;div class="document"&gt;
&lt;p&gt;I recently created a PEP to propose a new way of configuring logging, using dictionaries: &lt;a href="http://www.python.org/dev/peps/pep-0391/"&gt;PEP 391&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-892209330645689048?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/892209330645689048/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2009/10/proto-pep-for-logging-configuration.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/892209330645689048'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/892209330645689048'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2009/10/proto-pep-for-logging-configuration.html' title='PEP for logging configuration using dictionaries'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-8720168801478433151</id><published>2009-09-24T04:34:00.000-07:00</published><updated>2009-09-24T04:42:12.075-07:00</updated><title type='text'>How to treat a logger like an output stream</title><content type='html'>&lt;p&gt;Sometimes, you need to interface to a third-party API which expects a file-like object to write to, but you want to direct the API's output to a logger. You can do this using a LoggerWriter, which wraps a logger with a file-like API. Here's a short script illustrating the class:&lt;/p&gt;
&lt;div class="syntax"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerWriter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;br/&gt;
&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;demo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="n"&gt;info_fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggerWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="n"&gt;debug_fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggerWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;info_fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;An INFO message&amp;quot;&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;debug_fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;A DEBUG message&amp;quot;&lt;/span&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br/&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br/&gt;
&lt;/div&gt;
&lt;p&gt;When run, the script prints:
&lt;pre&gt;
INFO:demo:An INFO message
DEBUG:demo:An DEBUG message&lt;/pre&gt;
&lt;p&gt;Many thanks to &lt;a href="http://barry.warsaw.us/"&gt;Barry Warsaw&lt;/a&gt; for the suggestion.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-8720168801478433151?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/8720168801478433151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2009/09/how-to-treat-logger-like-output-stream.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8720168801478433151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/8720168801478433151'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2009/09/how-to-treat-logger-like-output-stream.html' title='How to treat a logger like an output stream'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8853457579684330224.post-6588029158193729294</id><published>2009-09-14T04:27:00.000-07:00</published><updated>2010-01-17T10:36:15.263-08:00</updated><title type='text'>Python Logging 101</title><content type='html'>&lt;br/&gt;
&lt;a name="intro"&gt;&lt;/a&gt;
&lt;h3&gt;
Introduction&lt;/h3&gt;
&lt;p&gt;The Merriam-Webster dictionary definition of the verb to log is:&lt;/p&gt;
&lt;blockquote&gt;
To make a note or record of : enter details of or about in a log&lt;/blockquote&gt;
&lt;p&gt;At its most basic, a log is a list of events which may be of interest to someone in the future. In the context of software development, a log is a list of events of interest which occur during execution of an application program. (I use the word events as a synonym for happenings, rather than in the sense of mouse movements or keystrokes.) You can define an event quite simply, by asking the questions&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What happened?&lt;/li&gt;
&lt;li&gt;When did it happen?&lt;/li&gt;
&lt;li&gt;Where did it happen (in which area of the application)?&lt;/li&gt;
&lt;li&gt;How important is it?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;&lt;/ol&gt;
&lt;p&gt;The first three of these are objective, but the last is subjective. Lots of things happen during program execution, but only some of these are worth recording in a log. Generally, the developers of an application are best placed to decide which events are worth logging, and of their relative importance in the scheme of things.&lt;/p&gt;
&lt;p&gt;Typically, logs are read by developers (when tracking down bugs), system administrators (as part of routine monitoring of systems), support desk staff (when dealing with particular support issues), and even end users (when they see an error message). Events that are of interest to one audience may not be interesting to another. The audience might even be dispersed across different geographical locations. A good logging system caters for all of these audiences by allowing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers to easily record events - what happened, when, where and how important it is.&lt;/li&gt;
&lt;li&gt;Flexible dissemination of event information to wherever an interested reader (whether human or another program) can read it.&lt;/li&gt;
&lt;li&gt;Flexibility of configuration, so that recording of events can be made conditional on where they occurred and how important they are, without changing the application code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basic logging of errors to text files and system logs is an old technique, but not very flexible. In this post, I introduce a logging system for the Python programming language. This system, while it borrows  ideas from other systems, is not a port of anything but an independent implementation for use by Python developers.The logging package has been part of Python since Python 2.3 (released in 2002).&lt;/p&gt;
&lt;p&gt;I'll cover each of the above points - recording events, disseminating events and configuring the system.&lt;/p&gt;
&lt;a name="recording-events"&gt;&lt;/a&gt;&lt;h3&gt;
Recording events&lt;/h3&gt;
&lt;p&gt;Recalling that an event was defined above in terms of four dimensions, the following table shows how these dimensions are provided to the logging API.&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td class="padright"&gt;What happened&lt;/td&gt;&lt;td&gt;This is passed in using a formatting string with optional arguments&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;When it happened&lt;/td&gt;&lt;td&gt;This is not passed in explicitly. The module assumes that the time you called the logging API was the time of the event.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;Where it happened&lt;/td&gt;&lt;td&gt;This is specified in terms of a &lt;em&gt;logging channel&lt;/em&gt; or &lt;em&gt;logger name&lt;/em&gt; (defined below).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;How important it is&lt;/td&gt;&lt;td&gt;This is specified in terms of an integer &lt;em&gt;level&lt;/em&gt; (defined below).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;a name="loggers"&gt;&lt;/a&gt;&lt;h4&gt;
Loggers&lt;/h4&gt;
A &lt;em&gt;logging channel&lt;/em&gt; indicates an area of an application. How an &lt;em&gt;area&lt;/em&gt; is defined is up to the developer. Since an application can have any number of areas, logging channels are identified by a unique string. Application areas can be nested (e.g. an area of &lt;em&gt;input processing&lt;/em&gt; might include sub-areas &lt;em&gt;read CSV files&lt;/em&gt;, &lt;em&gt;read XLS files&lt;/em&gt; and &lt;em&gt;read Gnumeric files&lt;/em&gt;). To cater for this nesting, channel names are organized into a namespace hierarchy where levels are separated by periods, much like Java or Python package namespaces. In the above instance, channel names might be "input" for the upper level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
Having determined the relevant application area, an application obtains a reference to a &lt;i&gt;logger object&lt;/i&gt;, which is specific to a particular channel name:
&lt;div class="syntax"&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;input.xls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;
You can call the above code from any module or function, and it will always return a reference to the same logger. This avoids the need to pass logger references between functions. If this is the first call specifying a particular channel name, then a new logger is created and returned to you; the same logger is returned on subsequent calls with that channel name.
The namespace hierarchy maps onto an equivalent hierarchy of loggers. The logger named "input.csv" would have a parent logger named "input". There is no requirement to instantiate all the loggers implied by a particular namespace - the system does that as and when needed. At the top of the hierarchy is a root logger which is created automatically, and used like any other logger.
Once you have obtained a reference to a logger, you are almost ready to start logging.
&lt;a href="http://www.blogger.com/post-edit.g?blogID=8853457579684330224&amp;amp;postID=6588029158193729294" name="levels"&gt;&lt;/a&gt;
&lt;h4&gt;
Levels&lt;/h4&gt;
By default, there are five levels of importance associated with logging events. Experience has shown that having more levels is unhelpful, since the choice of which level to assign to an event becomes subjective. The five levels are DEBUG, INFO, WARNING, ERROR and CRITICAL. Their significance is described in the following table.
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td class="padright"&gt;DEBUG&lt;/td&gt;&lt;td&gt;Detailed information, of no interest when everything is working well but invaluable when diagnosing problems.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;INFO&lt;/td&gt;&lt;td&gt;Affirmations that things are working as expected, e.g. "service has started" or "indexing run complete". Often ignored.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;WARNING&lt;/td&gt; &lt;td&gt;There may be a problem in the near future, and this gives advance warning of it. But the application is able to proceed normally.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;ERROR&lt;/td&gt;&lt;td&gt;The application has been unable to proceed as expected, due to the problem being logged.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;CRITICAL&lt;/td&gt; &lt;td&gt;This is a serious error, and some kind of application meltdown might be imminent.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The above categories will cater for most scenarios, though the module allows an application developer to define their own custom levels if they really need to.
Using the logging module couldn't be simpler. In any module which uses logging, you simply include the statement
&lt;div class="syntax"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;/div&gt;&lt;p&gt;and you can then log away:&lt;/p&gt;&lt;div class="syntax"&gt;
&lt;span class="n"&gt;inputLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;input&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;csvLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;input.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;csvLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Trying to read file &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;csvLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;File &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; contains no data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;csvLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;File &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;: unexpected end of file at line &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;, offset &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lineno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;csvLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;File &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;: too large, not enough memory, amount used = &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;If you are handling an exception, you can provide for a stack trace to be inserted into the log, as indicated by the three equivalent statements in the following listing. When the logger sees the &lt;i&gt;exc_info&lt;/i&gt; argument, it treats it as an indication that traceback information is desirable.&lt;/p&gt;
&lt;div class="syntax"&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Error reading file &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; at offset &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Error reading file &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; at offset &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Error reading file &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; at offset &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;
&lt;a href="" name="disseminating-events"&gt;&lt;/a&gt;&lt;br/&gt;&lt;h3&gt;
Disseminating events&lt;/h3&gt;
&lt;p&gt;So far, so good. We've got hold of some loggers, and told them about various events at various levels of importance. What happens to those logged events? That's where &lt;i&gt;handlers&lt;/i&gt; come in. Recall that a key component of a good logging system is getting the events to somewhere where interested parties can read them. In today's heterogeneous computing environments, where NT rubs shoulders with UNIX and multiple applications need to work together to deliver complex requirements, there are often requirements for event log information to be available on machines other than where the events were generated.&lt;/p&gt;
&lt;p&gt;Handlers allow flexible dissemination of logging events. To see how flexible, the following shows a list of the handlers currently provided with Python logging.&lt;/p&gt;
&lt;table style="vertical-align: top;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td class="padright"&gt;Handler&lt;/td&gt;&lt;td&gt;How it's used&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;StreamHandler&lt;/td&gt;&lt;td&gt;
Used to write to an output stream, typically sys.stdout or sys.stderr&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;FileHandler&lt;/td&gt;&lt;td&gt;
Inherits from StreamHandler to allow writing to a disk file.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;RotatingFileHandler&lt;/td&gt;&lt;td&gt;
Used for logging to a set of files, switching from one file to the next when the current file reaches a certain size.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;TimedRotatingFileHandler&lt;/td&gt;&lt;td&gt;
Used for logging to a set of files, switching from one file to the next at specified times.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;SocketHandler&lt;/td&gt;&lt;td&gt;
Used to send the events, via a socket, to a remote server listening on a TCP port.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;DatagramHandler&lt;/td&gt;&lt;td&gt;
Similar to SocketHandler, except that UDP sockets are used. There's less overhead but less reliability.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;SMTPHandler&lt;/td&gt;&lt;td&gt;
Used to send the events to designated e-mail addresses.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;SysLogHandler&lt;/td&gt;&lt;td&gt;
Used to send the events to a UNIX/Linux syslog.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;NTEventLogHandler&lt;/td&gt;&lt;td&gt;
Used to send the events to an NT event log.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;HTTPHandler&lt;/td&gt;&lt;td&gt;
Used to post the events to a Web server.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;MemoryHandler&lt;/td&gt;&lt;td&gt;
Used to buffer events in memory until a trigger is received, at which point the events are sent to another handler to deal with.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;NullHandler&lt;/td&gt;&lt;td&gt;
Used in library code which uses logging to avoid misconfiguration messages when used in an application which doesn't configure logging.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;These classes inherit from the base classes Handler and BufferingHandler, depending on whether they need to deal with events one at a time or whether they process events in batches.&lt;/p&gt;
&lt;p&gt;Handlers are associated with loggers in a flexible way. A logger can have handlers associated with it directly by means of the addHandler method (with a corresponding removeHandler method for dissociation, if required). But not every logger needs to be associated with a handler. This is because whenever a logging event is passed to a logger, the logger &lt;em&gt;and all of its parents&lt;/em&gt; are searched for handlers, and ALL these handlers are asked to handle the event. In order to get output from the logging system, all that is needed is that one or more handlers be associated with the root logger, and all loggers will automatically use those handlers.&lt;/p&gt;
&lt;p&gt;If no logger has any handlers, the system will indicate this to sys.stderr (as it's assumed to be a misconfiguration) and then keep quiet.&lt;/p&gt;
&lt;p&gt;If it is desired for a particular logger that handler search does not propagate to its parents, then the &lt;i&gt;propagate&lt;/i&gt; attribute of the logger can be set to 0. If this is done, the search for handlers will stop at that logger and not continue upwards through the hierarchy.&lt;/p&gt;
&lt;a name="handler-usage"&gt;&lt;/a&gt;&lt;h4&gt;
A Handler usage scenario&lt;/h4&gt;
&lt;p&gt;Suppose you want some events to be brought to the attention of developers, others operations staff, and yet others to the user. Then one way you could go about this is:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Configure an SMTPHandler with a developer email address, configured to pass ERROR events or worse.&lt;/li&gt;&lt;li&gt;Configure an SMTPHandler with a support desk email address, configured to pass CRITICAL events or worse.&lt;/li&gt;&lt;li&gt;Configure a FileHandler to pass all DEBUG events.&lt;/li&gt;&lt;li&gt;Configure your loggers however you want. For example, in production, you can set the topmost logger's level to WARNING, and you'll never see DEBUG messages anywhere. If you find there's a problem with a specific area of the application, you can set the logger for that area to send DEBUG events. Then you will see all events with levels &gt;= DEBUG from that area, but only events with levels &gt;= WARNING from the other areas.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;SMTPHandlers are generally used to notify people about problems which need urgent handling. If there's no urgency, then it's appropriate just to configure FileHandlers. It's not uncommon to have one log file for errors only and another log file which also includes DEBUG and INFO events.&lt;/p&gt;
&lt;a name="configuring-verbosity"&gt;&lt;/a&gt;&lt;h3&gt;
Configuring the level of output&lt;/h3&gt;
&lt;p&gt;Handlers and loggers are all very well, but we need to control the verbosity of output generated by the logging system. When a developer writes an application, they should add logging calls for every conceivable event an audience may be interested in. After all, if they don't do this, those events would never be captured. During application testing, most of these events will probably be logged at some point. However, when the application is shipped, suppose that a bug is reported. No one wants to wade through reams of detail looking for the key pointers to the problem - so the verbosity has to be turned right down across the application. As developers home in on the problem, they would like to be able to turn the verbosity up and down selectively in particular areas, to see what is happening. They should be able to do this without changing the application code in any way.&lt;/p&gt;
&lt;p&gt;The most basic form of verbosity control is provided by setting a threshold level on loggers, handlers or both. Both Logger and Handler have a setLevel method which takes a level and associates it with the logger or handler. When a logging call is made, if the logger's threshold is above the level of the call, no event is actually generated. For example, if a logger's threshold is set to ERROR, then debug(), info() and warning() calls on that logger do not generate any events, but error() and critical() do. Similarly, if a handler's threshold is set above the level of an event which is passed to it for handling, the handler ignores the event.&lt;/p&gt;
&lt;p&gt;From a performance point of view, it is better to apply thresholds at the logger level than the handler level. However, there may be times when levels must be applied at the handler level to get the desired effect.&lt;/p&gt;
&lt;p&gt;There is also an overall, high-level filter which can be applied to all loggers at one stroke. The module contains a function disable() which takes a level argument and acts as a threshold for all loggers. This setting is checked before the logger's own level setting.&lt;/p&gt;
&lt;a name="filters"&gt;&lt;/a&gt;&lt;h4&gt;
Filters&lt;/h4&gt;
&lt;p&gt;If simple level-based filtering of the kind described above is not enough, then you can instantiate Filter objects and associate them with both loggers and handlers. A filter object has a filter() method which is passed an event and which returns a value indicating whether the event is to be processed. Multiple filters can be associated with loggers and handlers, which both inherit from a base class Filterer having addFilter(), removeFilter() and filter() methods. The Filterer.filter() method calls the filter() method on all attached filters until all have seen the event, or until one of the filters rejects the event. If the event comes through unscathed, it is processed.&lt;/p&gt;
&lt;a name="log-records"&gt;&lt;/a&gt;&lt;h4&gt;
LogRecords&lt;/h4&gt;
&lt;p&gt;In the discussion so far, we have talked in general terms about &lt;em&gt;events&lt;/em&gt;. But how is an event represented? In this system, events are described by LogRecord instances. The LogRecord class has minimal functionality, acting as a repository for all the information of interest in an event; its single method getMessage() is intended to provide a hook for converting the message and arguments (passed to it at creation time) into a string representation of the event.&lt;/p&gt;
&lt;p&gt;The main information passed in a logging call is the level, a message and arguments for use with that message. All of this information is held in the LogRecord. There is also additional information which the system generates automatically, and all of the information in a LogRecord can appear in the final output from the system. Here's a list of the information currently held in a LogRecord:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logger name (logging channel - e.g. "input.csv")&lt;/li&gt;
&lt;li&gt;Event level (e.g. DEBUG)&lt;/li&gt;
&lt;li&gt;Event level text (e.g. "DEBUG")&lt;/li&gt;
&lt;li&gt;Pathname of source file from where the logging call was issued&lt;/li&gt;
&lt;li&gt;Filename of source file from where the logging call was issued&lt;/li&gt;
&lt;li&gt;Line number in source file where the logging call was issued&lt;/li&gt;
&lt;li&gt;Name of function from which logging call was issued&lt;/li&gt;
&lt;li&gt;Time when the event (LogRecord) was created (as seconds after the epoch)&lt;/li&gt;
&lt;li&gt;Millisecond portion of the creation time&lt;/li&gt;
&lt;li&gt;Creation time relative to when the logging module was loaded (typically, application startup time)&lt;/li&gt;
&lt;li&gt;Thread id (available if threading is available on your platform)&lt;/li&gt;
&lt;li&gt;Process id&lt;/li&gt;
&lt;/ul&gt;
&lt;a name="message-objects"&gt;&lt;/a&gt;&lt;h4&gt;
Message Objects&lt;/h4&gt;
&lt;p&gt;In the preceding discussion and examples, it has been assumed that the message passed when logging the event is a string. However, this is not the only possibility. You can pass &lt;b&gt;an arbitrary object&lt;/b&gt; as a message, and its __str__() method will be called when needed to convert it to a string representation. In fact, if you want to, you can avoid computing a string representation altogether - for example, the SocketHandler emits an event by pickling it and sending it over the wire.&lt;/p&gt;&lt;h4&gt;
Event Processing&lt;/h4&gt;
&lt;p&gt;Let's assume that an event has been generated, and needs to be processed. What do I mean by &lt;em&gt;processing&lt;/em&gt;? All that's left to do is to format the event appropriately for the target audience, and then output it to the relevant sink.&lt;/p&gt;
&lt;p&gt;In almost all cases, this means formatting the event into some form of text string. The actual formatting is done at the last moment, to avoid unnecessary processing. Formatting is controlled through the use of Formatter objects, which are associated with handlers using the setFormatter() method.&lt;/p&gt;
&lt;a name="output-formats"&gt;&lt;/a&gt;&lt;h4&gt;
Controlling Output Formats&lt;/h4&gt;
&lt;p&gt;Formatters control the formatting of an event into text. There are two base classes - Formatter (which works on single events) and BufferingFormatter (which works on a set of events, and includes the ability to add header and trailer text). Formatters use Python's powerful % operator (similar to C's sprintf) to format the output flexibly.&lt;/p&gt;
&lt;p&gt;Formatters know how a LogRecord is laid out - they know the field names. If you specify a format string such as "%(asctime)s %(level)-5s %(message)s", this would output the time, level of the logging event and the user's message (itself obtained by evaluating &lt;em&gt;msg % args&lt;/em&gt; where msg and args were specified by the user).&lt;/p&gt;
&lt;p&gt;Formatters are initialized with a format string and an optional date/time format string. The latter is used as an argument to the standard library strftime function. Formatters try to avoid needless processing - for example, the creation time is formatted into text using the date format string only if the main format string contains "%(asctime)s". If exception information is required and available, it is formatted using the standard traceback module and appended to the formatted string. It's also cached for subsequent format operations.&lt;/p&gt;
&lt;a name="optimization"&gt;&lt;/a&gt;&lt;h4&gt;
Optimization&lt;/h4&gt;
&lt;p&gt;Formatting of message arguments is deferred until it cannot be avoided. However, computing the arguments passed to the logging method can also be expensive, and you may want to avoid doing it if the logger will just throw away your event. To decide what to do, you can call the isEnabledFor method which takes a level argument and returns true if the event would be created by the Logger for that level of call. You can write code like this:&lt;/p&gt;
&lt;div class="syntax"&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnabledFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Message with &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expensive_func1&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;expensive_func2&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;so that if the logger's threshold is set above DEBUG, the calls to expensive_func1 and expensive_func2 are never made.&lt;/p&gt;
&lt;p&gt;There are other optimizations which can be made for specific applications which need more precise control over what logging information is collected. Here's a list of things you can do to avoid processing during logging which you don't need:&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td class="padright"&gt;You don't want information about where calls were made from.&lt;/td&gt;&lt;td&gt;Set logging._srcfile to None.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;You don't want threading information in the log.&lt;/td&gt;&lt;td&gt;Set logging.logThreads to 0.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="padright"&gt;You don't want process information in the log.&lt;/td&gt;&lt;td&gt;Set logging.logProcesses to 0.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Also note that the core logging module only includes the basic handlers. If you don't import &lt;em&gt;logging.handlers&lt;/em&gt; and &lt;em&gt;logging.config&lt;/em&gt;, they stay out of your way.&lt;/p&gt;
&lt;a name="threading"&gt;&lt;/a&gt;&lt;h3&gt;Threading&lt;/h3&gt;
&lt;p&gt;The logging module doesn't use threads itself, but it ensures that it is thread-safe by using a reentrant lock (threading.RLock) to serialize access to internal data structures, as well as to I/O handlers. Each handler instance gets a reentrant lock, and each operation to emit an event is bracketed with a lock acquisition and release.&lt;/p&gt;
&lt;a name="convenience-functions"&gt;&lt;/a&gt;&lt;h3&gt;
Convenience Functions&lt;/h3&gt;
&lt;p&gt;For casual users of the logging system, or Python novices, it may be too much trouble to create loggers in a namespace hierarchy. For such modes of use, the module defines module level functions debug(), info(), warning(), error() and critical() which delegate to the root logger. If no handler has been configured for this logger, then basicConfig() is called to attach a handler to the root logger. This means that the very simplest use of the logging module is as indicated:&lt;/p&gt;
&lt;div class="syntax"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Here&amp;#39;s some &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; information about &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;debugging&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Here&amp;#39;s some &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;information&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;This is your first &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;warning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;To &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; is human&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;err&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The situation is getting &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;critical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br/&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Please add a &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; to this message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;stack traceback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;
&lt;br/&gt;
&lt;a name="config"&gt;&lt;/a&gt;&lt;h3&gt;What's with the config stuff?&lt;/h3&gt;
&lt;p&gt;The configuration system in the &lt;em&gt;logging.config&lt;/em&gt; is a very basic implementation of a one-shot configuration, not likely to be of much help in sophisticated usage but providing some value for novice and/or casual users. If you don't want to use it, don't - the entire logging system can be programmatically configured, and if you want you can have your own configuration file format and load that using your own code, making logging API calls to actually do the configing of loggers, handlers etc. The implementation uses ConfigParser (in order to avoid having yet another configuration meta-format) and so the format is more verbose than it could be.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8853457579684330224-6588029158193729294?l=plumberjack.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://plumberjack.blogspot.com/feeds/6588029158193729294/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://plumberjack.blogspot.com/2009/09/python-logging-101.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6588029158193729294'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8853457579684330224/posts/default/6588029158193729294'/><link rel='alternate' type='text/html' href='http://plumberjack.blogspot.com/2009/09/python-logging-101.html' title='Python Logging 101'/><author><name>Vinay Sajip</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_-N8xZeqqzq4/SsMgEc9n3EI/AAAAAAAABG4/nUz8FxtYvoU/s1600-R/7094252405dd3dd5f798b132834d39b2%3Fs%3D128%26d%3Didenticon%26r%3DPG'/></author><thr:total>9</thr:total></entry></feed>
