2639 lines
352 KiB
HTML
2639 lines
352 KiB
HTML
|
||
<!DOCTYPE html>
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh_CN">
|
||
<head>
|
||
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||
|
||
<title>日志专题手册 — Python 3.8.20 文档</title><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<link rel="stylesheet" href="../_static/pydoctheme.css" type="text/css" />
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
|
||
|
||
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
|
||
<script src="../_static/jquery.js"></script>
|
||
<script src="../_static/underscore.js"></script>
|
||
<script src="../_static/doctools.js"></script>
|
||
<script src="../_static/language_data.js"></script>
|
||
<script src="../_static/translations.js"></script>
|
||
|
||
<script src="../_static/sidebar.js"></script>
|
||
|
||
<link rel="search" type="application/opensearchdescription+xml"
|
||
title="在 Python 3.8.20 文档 中搜索"
|
||
href="../_static/opensearch.xml"/>
|
||
<link rel="author" title="关于这些文档" href="../about.html" />
|
||
<link rel="index" title="索引" href="../genindex.html" />
|
||
<link rel="search" title="搜索" href="../search.html" />
|
||
<link rel="copyright" title="版权所有" href="../copyright.html" />
|
||
<link rel="next" title="正则表达式指南" href="regex.html" />
|
||
<link rel="prev" title="日志指南" href="logging.html" />
|
||
<link rel="canonical" href="https://docs.python.org/3/howto/logging-cookbook.html" />
|
||
|
||
|
||
|
||
|
||
|
||
<style>
|
||
@media only screen {
|
||
table.full-width-table {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
<link rel="shortcut icon" type="image/png" href="../_static/py.svg" />
|
||
<script type="text/javascript" src="../_static/copybutton.js"></script>
|
||
<script type="text/javascript" src="../_static/menu.js"></script>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="mobile-nav">
|
||
<input type="checkbox" id="menuToggler" class="toggler__input" aria-controls="navigation"
|
||
aria-pressed="false" aria-expanded="false" role="button" aria-label="Menu" />
|
||
<label for="menuToggler" class="toggler__label">
|
||
<span></span>
|
||
</label>
|
||
<nav class="nav-content" role="navigation">
|
||
<a href="https://www.python.org/" class="nav-logo">
|
||
<img src="../_static/py.svg" alt="Logo"/>
|
||
</a>
|
||
<div class="version_switcher_placeholder"></div>
|
||
<form role="search" class="search" action="../search.html" method="get">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="search-icon">
|
||
<path fill-rule="nonzero"
|
||
d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 001.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 00-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 005.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="#444"></path>
|
||
</svg>
|
||
<input type="text" name="q" aria-label="快速搜索"/>
|
||
<input type="submit" value="转向"/>
|
||
</form>
|
||
</nav>
|
||
<div class="menu-wrapper">
|
||
<nav class="menu" role="navigation" aria-label="main navigation">
|
||
<div class="language_switcher_placeholder"></div>
|
||
<h3><a href="../contents.html">目录</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">日志专题手册</a><ul>
|
||
<li><a class="reference internal" href="#using-logging-in-multiple-modules">在多模块中使用日志</a></li>
|
||
<li><a class="reference internal" href="#logging-from-multiple-threads">在多个线程中记录日志</a></li>
|
||
<li><a class="reference internal" href="#multiple-handlers-and-formatters">多个 handler 和多种 formatter</a></li>
|
||
<li><a class="reference internal" href="#logging-to-multiple-destinations">在多个地方记录日志</a></li>
|
||
<li><a class="reference internal" href="#configuration-server-example">日志配置服务器示例</a></li>
|
||
<li><a class="reference internal" href="#dealing-with-handlers-that-block">处理日志 handler 的阻塞</a></li>
|
||
<li><a class="reference internal" href="#sending-and-receiving-logging-events-across-a-network">通过网络收发日志事件</a></li>
|
||
<li><a class="reference internal" href="#adding-contextual-information-to-your-logging-output">在自己的输出日志中添加上下文信息</a><ul>
|
||
<li><a class="reference internal" href="#using-loggeradapters-to-impart-contextual-information">利用 LoggerAdapter 传递上下文信息</a><ul>
|
||
<li><a class="reference internal" href="#using-objects-other-than-dicts-to-pass-contextual-information">使用除字典之外的其它对象传递上下文信息</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#using-filters-to-impart-contextual-information">使用过滤器传递上下文信息</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#logging-to-a-single-file-from-multiple-processes">从多个进程记录至单个文件</a><ul>
|
||
<li><a class="reference internal" href="#using-concurrent-futures-processpoolexecutor">concurrent.futures.ProcessPoolExecutor 的用法</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#using-file-rotation">轮换日志文件</a></li>
|
||
<li><a class="reference internal" href="#use-of-alternative-formatting-styles">使用其他日志格式化方式</a></li>
|
||
<li><a class="reference internal" href="#customizing-logrecord">自定义 <code class="docutils literal notranslate"><span class="pre">LogRecord</span></code></a></li>
|
||
<li><a class="reference internal" href="#subclassing-queuehandler-a-zeromq-example">子类化 QueueHandler - ZeroMQ 示例</a></li>
|
||
<li><a class="reference internal" href="#subclassing-queuelistener-a-zeromq-example">子类化 QueueListener —— ZeroMQ 示例</a></li>
|
||
<li><a class="reference internal" href="#an-example-dictionary-based-configuration">基于字典进行日志配置的示例</a></li>
|
||
<li><a class="reference internal" href="#using-a-rotator-and-namer-to-customize-log-rotation-processing">利用 rotator 和 namer 自定义日志轮换操作</a></li>
|
||
<li><a class="reference internal" href="#a-more-elaborate-multiprocessing-example">更加详细的多道处理示例</a></li>
|
||
<li><a class="reference internal" href="#inserting-a-bom-into-messages-sent-to-a-sysloghandler">在发送给 SysLogHandler 的信息中插入一个 BOM。</a></li>
|
||
<li><a class="reference internal" href="#implementing-structured-logging">结构化日志的实现代码</a></li>
|
||
<li><a class="reference internal" href="#customizing-handlers-with-dictconfig">利用 <code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code> 自定义 handler</a></li>
|
||
<li><a class="reference internal" href="#using-particular-formatting-styles-throughout-your-application">生效于整个应用程序的格式化样式</a><ul>
|
||
<li><a class="reference internal" href="#using-logrecord-factories">LogRecord 工厂的用法</a></li>
|
||
<li><a class="reference internal" href="#using-custom-message-objects">自定义信息对象的使用</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#configuring-filters-with-dictconfig">利用 <code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code> 定义过滤器</a></li>
|
||
<li><a class="reference internal" href="#customized-exception-formatting">异常信息的自定义格式化</a></li>
|
||
<li><a class="reference internal" href="#speaking-logging-messages">语音播报日志信息</a></li>
|
||
<li><a class="reference internal" href="#buffering-logging-messages-and-outputting-them-conditionally">缓冲日志消息并有条件地输出它们</a></li>
|
||
<li><a class="reference internal" href="#formatting-times-using-utc-gmt-via-configuration">通过配置使用UTC (GMT) 格式化时间</a></li>
|
||
<li><a class="reference internal" href="#using-a-context-manager-for-selective-logging">使用上下文管理器的可选的日志记录</a></li>
|
||
<li><a class="reference internal" href="#a-cli-application-starter-template">命令行日志应用起步</a></li>
|
||
<li><a class="reference internal" href="#a-qt-gui-for-logging">Qt GUI 日志示例</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<h4>上一个主题</h4>
|
||
<p class="topless"><a href="logging.html"
|
||
title="上一章">日志指南</a></p>
|
||
<h4>下一个主题</h4>
|
||
<p class="topless"><a href="regex.html"
|
||
title="下一章">正则表达式指南</a></p>
|
||
<div role="note" aria-label="source link">
|
||
<h3>本页</h3>
|
||
<ul class="this-page-menu">
|
||
<li><a href="../bugs.html">报告 Bug</a></li>
|
||
<li>
|
||
<a href="https://github.com/python/cpython/blob/3.8/Doc/howto/logging-cookbook.rst"
|
||
rel="nofollow">显示源代码
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="outdated-warning" style="padding: .5em; text-align: center; background-color: #FFBABA; color: #6A0E0E;">
|
||
这个文档所针对的是一个已不再受支持的 Python 旧版本。
|
||
你应当升级版本,并阅读
|
||
<a href="/3/howto/logging-cookbook.html"> Python 当前稳定版本的文档</a>.
|
||
</div>
|
||
|
||
<div class="related" role="navigation" aria-label="related navigation">
|
||
<h3>导航</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="总目录"
|
||
accesskey="I">索引</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python 模块索引"
|
||
>模块</a> |</li>
|
||
<li class="right" >
|
||
<a href="regex.html" title="正则表达式指南"
|
||
accesskey="N">下一页</a> |</li>
|
||
<li class="right" >
|
||
<a href="logging.html" title="日志指南"
|
||
accesskey="P">上一页</a> |</li>
|
||
|
||
<li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li>
|
||
<li><a href="https://www.python.org/">Python</a> »</li>
|
||
<li class="switchers">
|
||
<div class="language_switcher_placeholder"></div>
|
||
<div class="version_switcher_placeholder"></div>
|
||
</li>
|
||
<li>
|
||
|
||
</li>
|
||
<li id="cpython-language-and-version">
|
||
<a href="../index.html">3.8.20 Documentation</a> »
|
||
</li>
|
||
|
||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Python 指南</a> »</li>
|
||
<li class="right">
|
||
|
||
|
||
<div class="inline-search" role="search">
|
||
<form class="inline-search" action="../search.html" method="get">
|
||
<input placeholder="快速搜索" aria-label="快速搜索" type="text" name="q" />
|
||
<input type="submit" value="转向" />
|
||
<input type="hidden" name="check_keywords" value="yes" />
|
||
<input type="hidden" name="area" value="default" />
|
||
</form>
|
||
</div>
|
||
|
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
<div class="body" role="main">
|
||
|
||
<section id="logging-cookbook">
|
||
<span id="id1"></span><h1>日志专题手册<a class="headerlink" href="#logging-cookbook" title="永久链接至标题">¶</a></h1>
|
||
<dl class="field-list simple">
|
||
<dt class="field-odd">作者</dt>
|
||
<dd class="field-odd"><p>Vinay Sajip <vinay_sajip at red-dove dot com></p>
|
||
</dd>
|
||
</dl>
|
||
<p>本页包含了许多日志记录相关的概念,这些概念在过去一直被认为很有用。</p>
|
||
<section id="using-logging-in-multiple-modules">
|
||
<h2>在多模块中使用日志<a class="headerlink" href="#using-logging-in-multiple-modules" title="永久链接至标题">¶</a></h2>
|
||
<p>无论对 <code class="docutils literal notranslate"><span class="pre">logging.getLogger('someLogger')</span></code> 进行多少次调用,都会返回同一个 logger 对象的引用。不仅在同一个模块内如此,只要是在同一个 Python 解释器进程中,跨模块调用也是一样。同样是引用同一个对象,应用程序也可以在一个模块中定义和配置一个父 logger,而在另一个单独的模块中创建(但不配置)子 logger,对于子 logger 的所有调用都会传给父 logger。以下是主模块:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">auxiliary_module</span>
|
||
|
||
<span class="c1"># create logger with 'spam_application'</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'spam_application'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="c1"># create file handler which logs even debug messages</span>
|
||
<span class="n">fh</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="s1">'spam.log'</span><span class="p">)</span>
|
||
<span class="n">fh</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="c1"># create console handler with a higher log level</span>
|
||
<span class="n">ch</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="n">ch</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">)</span>
|
||
<span class="c1"># create formatter and add it to the handlers</span>
|
||
<span class="n">formatter</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> - </span><span class="si">%(name)s</span><span class="s1"> - </span><span class="si">%(levelname)s</span><span class="s1"> - </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">fh</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="n">ch</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="c1"># add the handlers to the logger</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">fh</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span>
|
||
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'creating an instance of auxiliary_module.Auxiliary'</span><span class="p">)</span>
|
||
<span class="n">a</span> <span class="o">=</span> <span class="n">auxiliary_module</span><span class="o">.</span><span class="n">Auxiliary</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'created an instance of auxiliary_module.Auxiliary'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'calling auxiliary_module.Auxiliary.do_something'</span><span class="p">)</span>
|
||
<span class="n">a</span><span class="o">.</span><span class="n">do_something</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'finished auxiliary_module.Auxiliary.do_something'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'calling auxiliary_module.some_function()'</span><span class="p">)</span>
|
||
<span class="n">auxiliary_module</span><span class="o">.</span><span class="n">some_function</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'done with auxiliary_module.some_function()'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>以下是辅助模块:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="c1"># create logger</span>
|
||
<span class="n">module_logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'spam_application.auxiliary'</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">Auxiliary</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'spam_application.auxiliary.Auxiliary'</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'creating an instance of Auxiliary'</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">do_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'doing something'</span><span class="p">)</span>
|
||
<span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'done doing something'</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">some_function</span><span class="p">():</span>
|
||
<span class="n">module_logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'received a call to "some_function"'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>输出结果会像这样:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>2005-03-23 23:47:11,663 - spam_application - INFO -
|
||
creating an instance of auxiliary_module.Auxiliary
|
||
2005-03-23 23:47:11,665 - spam_application.auxiliary.Auxiliary - INFO -
|
||
creating an instance of Auxiliary
|
||
2005-03-23 23:47:11,665 - spam_application - INFO -
|
||
created an instance of auxiliary_module.Auxiliary
|
||
2005-03-23 23:47:11,668 - spam_application - INFO -
|
||
calling auxiliary_module.Auxiliary.do_something
|
||
2005-03-23 23:47:11,668 - spam_application.auxiliary.Auxiliary - INFO -
|
||
doing something
|
||
2005-03-23 23:47:11,669 - spam_application.auxiliary.Auxiliary - INFO -
|
||
done doing something
|
||
2005-03-23 23:47:11,670 - spam_application - INFO -
|
||
finished auxiliary_module.Auxiliary.do_something
|
||
2005-03-23 23:47:11,671 - spam_application - INFO -
|
||
calling auxiliary_module.some_function()
|
||
2005-03-23 23:47:11,672 - spam_application.auxiliary - INFO -
|
||
received a call to 'some_function'
|
||
2005-03-23 23:47:11,673 - spam_application - INFO -
|
||
done with auxiliary_module.some_function()
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="logging-from-multiple-threads">
|
||
<h2>在多个线程中记录日志<a class="headerlink" href="#logging-from-multiple-threads" title="永久链接至标题">¶</a></h2>
|
||
<p>多线程记录日志并不需要特殊处理,以下示例演示了在主线程(起始线程)和其他线程中记录日志的过程:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">threading</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">arg</span><span class="p">):</span>
|
||
<span class="k">while</span> <span class="ow">not</span> <span class="n">arg</span><span class="p">[</span><span class="s1">'stop'</span><span class="p">]:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Hi from myfunc'</span><span class="p">)</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(relativeCreated)6d</span><span class="s1"> </span><span class="si">%(threadName)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">info</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'stop'</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span>
|
||
<span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">info</span><span class="p">,))</span>
|
||
<span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Hello from main'</span><span class="p">)</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.75</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||
<span class="n">info</span><span class="p">[</span><span class="s1">'stop'</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
|
||
<span class="k">break</span>
|
||
<span class="n">thread</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>脚本会运行输出类似下面的内容:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> 0 Thread-1 Hi from myfunc
|
||
3 MainThread Hello from main
|
||
505 Thread-1 Hi from myfunc
|
||
755 MainThread Hello from main
|
||
1007 Thread-1 Hi from myfunc
|
||
1507 MainThread Hello from main
|
||
1508 Thread-1 Hi from myfunc
|
||
2010 Thread-1 Hi from myfunc
|
||
2258 MainThread Hello from main
|
||
2512 Thread-1 Hi from myfunc
|
||
3009 MainThread Hello from main
|
||
3013 Thread-1 Hi from myfunc
|
||
3515 Thread-1 Hi from myfunc
|
||
3761 MainThread Hello from main
|
||
4017 Thread-1 Hi from myfunc
|
||
4513 MainThread Hello from main
|
||
4518 Thread-1 Hi from myfunc
|
||
</pre></div>
|
||
</div>
|
||
<p>以上如期显示了不同线程的日志是交替输出的。当然更多的线程也会如此。</p>
|
||
</section>
|
||
<section id="multiple-handlers-and-formatters">
|
||
<h2>多个 handler 和多种 formatter<a class="headerlink" href="#multiple-handlers-and-formatters" title="永久链接至标题">¶</a></h2>
|
||
<p>日志是个普通的 Python 对象。 <a class="reference internal" href="../library/logging.html#logging.Logger.addHandler" title="logging.Logger.addHandler"><code class="xref py py-meth docutils literal notranslate"><span class="pre">addHandler()</span></code></a> 方法可加入不限数量的日志 handler。有时候,应用程序需把严重错误信息记入文本文件,而将一般错误或其他级别的信息输出到控制台。若要进行这样的设定,只需多配置几个日志 handler 即可,应用程序的日志调用代码可以保持不变。下面对之前的分模块日志示例略做修改:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'simple_example'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="c1"># create file handler which logs even debug messages</span>
|
||
<span class="n">fh</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="s1">'spam.log'</span><span class="p">)</span>
|
||
<span class="n">fh</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="c1"># create console handler with a higher log level</span>
|
||
<span class="n">ch</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="n">ch</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">)</span>
|
||
<span class="c1"># create formatter and add it to the handlers</span>
|
||
<span class="n">formatter</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> - </span><span class="si">%(name)s</span><span class="s1"> - </span><span class="si">%(levelname)s</span><span class="s1"> - </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">ch</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="n">fh</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="c1"># add the handlers to logger</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">fh</span><span class="p">)</span>
|
||
|
||
<span class="c1"># 'application' code</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'debug message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'info message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'warn message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'error message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'critical message'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>需要注意的是,“应用程序”内的代码并不关心是否存在多个日志 handler。示例中所做的改变,只是新加入并配置了一个名为 <em>fh</em> 的 handler。</p>
|
||
<p>在编写和测试应用程序时,若能创建日志 handler 对不同严重级别的日志信息进行过滤,这将十分有用。调试时无需用多条 <code class="docutils literal notranslate"><span class="pre">print</span></code> 语句,而是采用 <code class="docutils literal notranslate"><span class="pre">logger.debug</span></code> :print 语句以后还得注释或删掉,而 logger.debug 语句可以原样留在源码中保持静默。当需要再次调试时,只要改变日志对象或 handler 的严重级别即可。</p>
|
||
</section>
|
||
<section id="logging-to-multiple-destinations">
|
||
<span id="multiple-destinations"></span><h2>在多个地方记录日志<a class="headerlink" href="#logging-to-multiple-destinations" title="永久链接至标题">¶</a></h2>
|
||
<p>假定要根据不同的情况将日志以不同的格式写入控制台和文件。比如把 DEBUG 以上级别的日志信息写于文件,并且把 INFO 以上的日志信息输出到控制台。再假设日志文件需要包含时间戳,控制台信息则不需要。以下演示了做法:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="c1"># set up logging to file - see previous section for more details</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span>
|
||
<span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(name)-12s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="n">datefmt</span><span class="o">=</span><span class="s1">'%m-</span><span class="si">%d</span><span class="s1"> %H:%M'</span><span class="p">,</span>
|
||
<span class="n">filename</span><span class="o">=</span><span class="s1">'/temp/myapp.log'</span><span class="p">,</span>
|
||
<span class="n">filemode</span><span class="o">=</span><span class="s1">'w'</span><span class="p">)</span>
|
||
<span class="c1"># define a Handler which writes INFO messages or higher to the sys.stderr</span>
|
||
<span class="n">console</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="n">console</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
|
||
<span class="c1"># set a format which is simpler for console use</span>
|
||
<span class="n">formatter</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(name)-12s</span><span class="s1">: </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="c1"># tell the handler to use this format</span>
|
||
<span class="n">console</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="c1"># add the handler to the root logger</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">console</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Now, we can log to the root logger, or any other logger. First the root...</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Jackdaws love my big sphinx of quartz.'</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Now, define a couple of other loggers which might represent areas in your</span>
|
||
<span class="c1"># application:</span>
|
||
|
||
<span class="n">logger1</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'myapp.area1'</span><span class="p">)</span>
|
||
<span class="n">logger2</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'myapp.area2'</span><span class="p">)</span>
|
||
|
||
<span class="n">logger1</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Quick zephyrs blow, vexing daft Jim.'</span><span class="p">)</span>
|
||
<span class="n">logger1</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'How quickly daft jumping zebras vex.'</span><span class="p">)</span>
|
||
<span class="n">logger2</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'Jail zesty vixen who grabbed pay from quack.'</span><span class="p">)</span>
|
||
<span class="n">logger2</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'The five boxing wizards jump quickly.'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>当运行后,你会看到控制台如下所示</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>root : INFO Jackdaws love my big sphinx of quartz.
|
||
myapp.area1 : INFO How quickly daft jumping zebras vex.
|
||
myapp.area2 : WARNING Jail zesty vixen who grabbed pay from quack.
|
||
myapp.area2 : ERROR The five boxing wizards jump quickly.
|
||
</pre></div>
|
||
</div>
|
||
<p>而日志文件将如下所示:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>10-22 22:19 root INFO Jackdaws love my big sphinx of quartz.
|
||
10-22 22:19 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim.
|
||
10-22 22:19 myapp.area1 INFO How quickly daft jumping zebras vex.
|
||
10-22 22:19 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack.
|
||
10-22 22:19 myapp.area2 ERROR The five boxing wizards jump quickly.
|
||
</pre></div>
|
||
</div>
|
||
<p>如您所见,DEBUG 级别的日志信息只出现在了文件中,而其他信息则两个地方都会输出。</p>
|
||
<p>上述示例只用到了控制台和文件 handler,当然还可以自由组合任意数量的日志 handler。</p>
|
||
</section>
|
||
<section id="configuration-server-example">
|
||
<h2>日志配置服务器示例<a class="headerlink" href="#configuration-server-example" title="永久链接至标题">¶</a></h2>
|
||
<p>以下是一个用到了日志配置服务器的模块示例:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.config</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
<span class="kn">import</span> <span class="nn">os</span>
|
||
|
||
<span class="c1"># read initial config file</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">fileConfig</span><span class="p">(</span><span class="s1">'logging.conf'</span><span class="p">)</span>
|
||
|
||
<span class="c1"># create and start listener on port 9999</span>
|
||
<span class="n">t</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">9999</span><span class="p">)</span>
|
||
<span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'simpleExample'</span><span class="p">)</span>
|
||
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="c1"># loop through logging calls to see the difference</span>
|
||
<span class="c1"># new configurations make, until Ctrl+C is pressed</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'debug message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'info message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'warn message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'error message'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'critical message'</span><span class="p">)</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||
<span class="c1"># cleanup</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">stopListening</span><span class="p">()</span>
|
||
<span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>以下脚本将接受文件名作为参数,然后将此文件发送到服务器,前面加上文件的二进制编码长度,做为新的日志配置:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
|
||
<span class="kn">import</span> <span class="nn">socket</span><span class="o">,</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">struct</span>
|
||
|
||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||
<span class="n">data_to_send</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
|
||
|
||
<span class="n">HOST</span> <span class="o">=</span> <span class="s1">'localhost'</span>
|
||
<span class="n">PORT</span> <span class="o">=</span> <span class="mi">9999</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'connecting...'</span><span class="p">)</span>
|
||
<span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">HOST</span><span class="p">,</span> <span class="n">PORT</span><span class="p">))</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'sending config...'</span><span class="p">)</span>
|
||
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s1">'>L'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">data_to_send</span><span class="p">)))</span>
|
||
<span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">data_to_send</span><span class="p">)</span>
|
||
<span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'complete'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="dealing-with-handlers-that-block">
|
||
<h2>处理日志 handler 的阻塞<a class="headerlink" href="#dealing-with-handlers-that-block" title="永久链接至标题">¶</a></h2>
|
||
<p>有时需让日志 handler 不要阻塞当前的线程。这在 Web 应用程序中比较常见,当然在其他场景中也会发生。</p>
|
||
<p>有一种原因往往会让程序表现迟钝,这就是 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SMTPHandler" title="logging.handlers.SMTPHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SMTPHandler</span></code></a>:由于很多因素是开发人员无法控制的(例如邮件或网络基础设施的性能不佳),发送电子邮件可能需要很长时间。不过几乎所有网络 handler 都可能会发生阻塞:即使是 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SocketHandler" title="logging.handlers.SocketHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SocketHandler</span></code></a> 操作也可能在后台执行 DNS 查询,而这种查询实在太慢了(并且 DNS 查询还可能在很底层的套接字库代码中,位于 Python 层之下,超出了可控范围)。</p>
|
||
<p>有一种解决方案是分成两部分实现。第一部分,针对那些对性能有要求的关键线程,只为日志对象连接一个 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueHandler" title="logging.handlers.QueueHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueHandler</span></code></a>。日志对象只需简单地写入队列即可,可为队列设置足够大的容量,或者可以在初始化时不设置容量上限。尽管为以防万一,可能需要在代码中捕获 <a class="reference internal" href="../library/queue.html#queue.Full" title="queue.Full"><code class="xref py py-exc docutils literal notranslate"><span class="pre">queue.Full</span></code></a> 异常,不过队列写入操作通常会很快得以处理。如果要开发库代码,包含性能要求较高的线程,为了让使用该库的开发人员受益,请务必在开发文档中进行标明(包括建议仅连接 <code class="docutils literal notranslate"><span class="pre">QueueHandlers</span></code> )。</p>
|
||
<p>解决方案的另一部分就是 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueListener" title="logging.handlers.QueueListener"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueListener</span></code></a>,它被设计为 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueHandler" title="logging.handlers.QueueHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueHandler</span></code></a> 的对应部分。<a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueListener" title="logging.handlers.QueueListener"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueListener</span></code></a> 非常简单:传入一个队列和一些 handler,并启动一个内部线程,用于侦听 <code class="docutils literal notranslate"><span class="pre">QueueHandlers</span></code> (或其他 <code class="docutils literal notranslate"><span class="pre">LogRecords</span></code> 源)发送的 LogRecord 队列。<code class="docutils literal notranslate"><span class="pre">LogRecords</span></code> 会从队列中移除并传给 handler 处理。</p>
|
||
<p><a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueListener" title="logging.handlers.QueueListener"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueListener</span></code></a> 作为单独的类,好处就是可以用同一个实例为多个 <code class="docutils literal notranslate"><span class="pre">QueueHandlers</span></code> 服务。这比把现有 handler 类线程化更加资源友好,后者会每个 handler 会占用一个线程,却没有特别的好处。</p>
|
||
<p>以下是这两个类的运用示例(省略了 import 语句):</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">que</span> <span class="o">=</span> <span class="n">queue</span><span class="o">.</span><span class="n">Queue</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># no limit on size</span>
|
||
<span class="n">queue_handler</span> <span class="o">=</span> <span class="n">QueueHandler</span><span class="p">(</span><span class="n">que</span><span class="p">)</span>
|
||
<span class="n">handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="n">listener</span> <span class="o">=</span> <span class="n">QueueListener</span><span class="p">(</span><span class="n">que</span><span class="p">,</span> <span class="n">handler</span><span class="p">)</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">queue_handler</span><span class="p">)</span>
|
||
<span class="n">formatter</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(threadName)s</span><span class="s1">: </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">handler</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="c1"># The log output will display the thread which generated</span>
|
||
<span class="c1"># the event (the main thread) rather than the internal</span>
|
||
<span class="c1"># thread which monitors the internal queue. This is what</span>
|
||
<span class="c1"># you want to happen.</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'Look out!'</span><span class="p">)</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>在运行后会产生:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>MainThread: Look out!
|
||
</pre></div>
|
||
</div>
|
||
<div class="versionchanged">
|
||
<p><span class="versionmodified changed">在 3.5 版更改: </span>在 Python 3.5 之前,<a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueListener" title="logging.handlers.QueueListener"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueListener</span></code></a> 总会把由队列接收到的每条信息都传递给已初始化的每个处理程序。(因为这里假定级别过滤操作已在写入队列时完成了。)从 3.5 版开始,可以修改这种处理方式,只要将关键字参数 <code class="docutils literal notranslate"><span class="pre">respect_handler_level=True</span></code> 传给侦听器的构造函数即可。这样侦听器将会把每条信息的级别与 handler 的级别进行比较,只在适配时才会将信息传给 handler 。</p>
|
||
</div>
|
||
</section>
|
||
<section id="sending-and-receiving-logging-events-across-a-network">
|
||
<span id="network-logging"></span><h2>通过网络收发日志事件<a class="headerlink" href="#sending-and-receiving-logging-events-across-a-network" title="永久链接至标题">¶</a></h2>
|
||
<p>假定现在要通过网络发送日志事件,并在接收端进行处理。有一种简单的方案,就是在发送端的根日志对象连接一个 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SocketHandler" title="logging.handlers.SocketHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SocketHandler</span></code></a> 实例:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span><span class="o">,</span> <span class="nn">logging.handlers</span>
|
||
|
||
<span class="n">rootLogger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span>
|
||
<span class="n">rootLogger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">socketHandler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">SocketHandler</span><span class="p">(</span><span class="s1">'localhost'</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">DEFAULT_TCP_LOGGING_PORT</span><span class="p">)</span>
|
||
<span class="c1"># don't bother with a formatter, since a socket handler sends the event as</span>
|
||
<span class="c1"># an unformatted pickle</span>
|
||
<span class="n">rootLogger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">socketHandler</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Now, we can log to the root logger, or any other logger. First the root...</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Jackdaws love my big sphinx of quartz.'</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Now, define a couple of other loggers which might represent areas in your</span>
|
||
<span class="c1"># application:</span>
|
||
|
||
<span class="n">logger1</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'myapp.area1'</span><span class="p">)</span>
|
||
<span class="n">logger2</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'myapp.area2'</span><span class="p">)</span>
|
||
|
||
<span class="n">logger1</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Quick zephyrs blow, vexing daft Jim.'</span><span class="p">)</span>
|
||
<span class="n">logger1</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'How quickly daft jumping zebras vex.'</span><span class="p">)</span>
|
||
<span class="n">logger2</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'Jail zesty vixen who grabbed pay from quack.'</span><span class="p">)</span>
|
||
<span class="n">logger2</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'The five boxing wizards jump quickly.'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>在接收端,可以用 <a class="reference internal" href="../library/socketserver.html#module-socketserver" title="socketserver: A framework for network servers."><code class="xref py py-mod docutils literal notranslate"><span class="pre">socketserver</span></code></a> 模块设置一个接收器。简要示例如下:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pickle</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.handlers</span>
|
||
<span class="kn">import</span> <span class="nn">socketserver</span>
|
||
<span class="kn">import</span> <span class="nn">struct</span>
|
||
|
||
|
||
<span class="k">class</span> <span class="nc">LogRecordStreamHandler</span><span class="p">(</span><span class="n">socketserver</span><span class="o">.</span><span class="n">StreamRequestHandler</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""Handler for a streaming logging request.</span>
|
||
|
||
<span class="sd"> This basically logs the record using whatever logging policy is</span>
|
||
<span class="sd"> configured locally.</span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Handle multiple requests - each expected to be a 4-byte length,</span>
|
||
<span class="sd"> followed by the LogRecord in pickle format. Logs the record</span>
|
||
<span class="sd"> according to whatever policy is configured locally.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">chunk</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span> <span class="o"><</span> <span class="mi">4</span><span class="p">:</span>
|
||
<span class="k">break</span>
|
||
<span class="n">slen</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">unpack</span><span class="p">(</span><span class="s1">'>L'</span><span class="p">,</span> <span class="n">chunk</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="n">chunk</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="n">slen</span><span class="p">)</span>
|
||
<span class="k">while</span> <span class="nb">len</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span> <span class="o"><</span> <span class="n">slen</span><span class="p">:</span>
|
||
<span class="n">chunk</span> <span class="o">=</span> <span class="n">chunk</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="n">slen</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">chunk</span><span class="p">))</span>
|
||
<span class="n">obj</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">unPickle</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
|
||
<span class="n">record</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">makeLogRecord</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handleLogRecord</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">unPickle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">handleLogRecord</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="c1"># if a name is specified, we use the named logger rather than the one</span>
|
||
<span class="c1"># implied by the record.</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">server</span><span class="o">.</span><span class="n">logname</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">server</span><span class="o">.</span><span class="n">logname</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="n">record</span><span class="o">.</span><span class="n">name</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="c1"># N.B. EVERY record gets logged. This is because Logger.handle</span>
|
||
<span class="c1"># is normally called AFTER logger-level filtering. If you want</span>
|
||
<span class="c1"># to do filtering, do it at the client end to save wasting</span>
|
||
<span class="c1"># cycles and network bandwidth!</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">handle</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">LogRecordSocketReceiver</span><span class="p">(</span><span class="n">socketserver</span><span class="o">.</span><span class="n">ThreadingTCPServer</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Simple TCP socket-based logging receiver suitable for testing.</span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="n">allow_reuse_address</span> <span class="o">=</span> <span class="kc">True</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">,</span>
|
||
<span class="n">port</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">DEFAULT_TCP_LOGGING_PORT</span><span class="p">,</span>
|
||
<span class="n">handler</span><span class="o">=</span><span class="n">LogRecordStreamHandler</span><span class="p">):</span>
|
||
<span class="n">socketserver</span><span class="o">.</span><span class="n">ThreadingTCPServer</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">),</span> <span class="n">handler</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">abort</span> <span class="o">=</span> <span class="mi">0</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logname</span> <span class="o">=</span> <span class="kc">None</span>
|
||
|
||
<span class="k">def</span> <span class="nf">serve_until_stopped</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="kn">import</span> <span class="nn">select</span>
|
||
<span class="n">abort</span> <span class="o">=</span> <span class="mi">0</span>
|
||
<span class="k">while</span> <span class="ow">not</span> <span class="n">abort</span><span class="p">:</span>
|
||
<span class="n">rd</span><span class="p">,</span> <span class="n">wr</span><span class="p">,</span> <span class="n">ex</span> <span class="o">=</span> <span class="n">select</span><span class="o">.</span><span class="n">select</span><span class="p">([</span><span class="bp">self</span><span class="o">.</span><span class="n">socket</span><span class="o">.</span><span class="n">fileno</span><span class="p">()],</span>
|
||
<span class="p">[],</span> <span class="p">[],</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">rd</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handle_request</span><span class="p">()</span>
|
||
<span class="n">abort</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">abort</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span>
|
||
<span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(relativeCreated)5d</span><span class="s1"> </span><span class="si">%(name)-15s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">tcpserver</span> <span class="o">=</span> <span class="n">LogRecordSocketReceiver</span><span class="p">()</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'About to start TCP server...'</span><span class="p">)</span>
|
||
<span class="n">tcpserver</span><span class="o">.</span><span class="n">serve_until_stopped</span><span class="p">()</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>先运行服务端,再运行客户端。客户端控制台不会显示什么信息;在服务端应该会看到如下内容:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>About to start TCP server...
|
||
59 root INFO Jackdaws love my big sphinx of quartz.
|
||
59 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim.
|
||
69 myapp.area1 INFO How quickly daft jumping zebras vex.
|
||
69 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack.
|
||
69 myapp.area2 ERROR The five boxing wizards jump quickly.
|
||
</pre></div>
|
||
</div>
|
||
<p>请注意,某些时候 pickle 会存在一些安全问题。若有问题可换用自己的序列化方案,只要覆盖 <code class="xref py py-meth docutils literal notranslate"><span class="pre">makePickle()</span></code> 方法即可,并调整上述脚本以采用自己的序列化方案。</p>
|
||
</section>
|
||
<section id="adding-contextual-information-to-your-logging-output">
|
||
<span id="context-info"></span><h2>在自己的输出日志中添加上下文信息<a class="headerlink" href="#adding-contextual-information-to-your-logging-output" title="永久链接至标题">¶</a></h2>
|
||
<p>有时,除了调用日志对象时传入的参数之外,还希望日志输出中能包含上下文信息。 比如在网络应用程序中,可能需要在日志中记录某客户端的信息(如远程客户端的用户名或 IP 地址)。 这虽然可以用 <em>extra</em> 参数实现,但传递起来并不总是很方便。 虽然为每个网络连接都创建 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 实例貌似不错,但并不是个好主意,因为这些实例不会被垃圾回收。 虽然在实践中不是问题,但当 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 实例的数量取决于应用程序要采用的日志粒度时,如果 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 实例的数量实际上是无限的,则有可能难以管理。</p>
|
||
<section id="using-loggeradapters-to-impart-contextual-information">
|
||
<h3>利用 LoggerAdapter 传递上下文信息<a class="headerlink" href="#using-loggeradapters-to-impart-contextual-information" title="永久链接至标题">¶</a></h3>
|
||
<p>要传递上下文信息和日志事件信息,有一种简单方案是利用 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 类。这个类设计得类似 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code>,所以可以直接调用 <code class="xref py py-meth docutils literal notranslate"><span class="pre">debug()</span></code>、<code class="xref py py-meth docutils literal notranslate"><span class="pre">info()</span></code>、 <code class="xref py py-meth docutils literal notranslate"><span class="pre">warning()</span></code>、 <code class="xref py py-meth docutils literal notranslate"><span class="pre">error()</span></code>、<code class="xref py py-meth docutils literal notranslate"><span class="pre">exception()</span></code>、 <code class="xref py py-meth docutils literal notranslate"><span class="pre">critical()</span></code> 和 <code class="xref py py-meth docutils literal notranslate"><span class="pre">log()</span></code>。这些方法的签名与 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 对应的方法相同,所以这两类实例可以交换使用。</p>
|
||
<p>当你创建一个 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 的实例时,你会传入一个 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 的实例和一个包含了上下文信息的字典对象。当你调用一个 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 实例的方法时,它会把调用委托给内部的 <code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code> 的实例,并为其整理相关的上下文信息。这是 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 的一个代码片段:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">debug</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Delegate a debug call to the underlying logger, after adding</span>
|
||
<span class="sd"> contextual information from this adapter instance.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">msg</span><span class="p">,</span> <span class="n">kwargs</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">process</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 的 <code class="xref py py-meth docutils literal notranslate"><span class="pre">process()</span></code> 方法是将上下文信息添加到日志的输出中。 它传入日志消息和日志调用的关键字参数,并传回(隐式的)这些修改后的内容去调用底层的日志记录器。此方法的默认参数只是一个消息字段,但留有一个 'extra' 的字段作为关键字参数传给构造器。当然,如果你在调用适配器时传入了一个 'extra' 字段的参数,它会被静默覆盖。</p>
|
||
<p>使用 'extra' 的优点是这些键值对会被传入 <code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code> 实例的 __dict__ 中,让你通过 <code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code> 的实例直接使用定制的字符串,实例能找到这个字典类对象的键。 如果你需要一个其他的方法,比如说,想要在消息字符串前后增加上下文信息,你只需要创建一个 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 的子类,并覆盖它的 <code class="xref py py-meth docutils literal notranslate"><span class="pre">process()</span></code> 方法来做你想做的事情,以下是一个简单的示例:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CustomAdapter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">LoggerAdapter</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> This example adapter expects the passed in dict-like object to have a</span>
|
||
<span class="sd"> 'connid' key, whose value in brackets is prepended to the log message.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="s1">'[</span><span class="si">%s</span><span class="s1">] </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">extra</span><span class="p">[</span><span class="s1">'connid'</span><span class="p">],</span> <span class="n">msg</span><span class="p">),</span> <span class="n">kwargs</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>你可以这样使用:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
<span class="n">adapter</span> <span class="o">=</span> <span class="n">CustomAdapter</span><span class="p">(</span><span class="n">logger</span><span class="p">,</span> <span class="p">{</span><span class="s1">'connid'</span><span class="p">:</span> <span class="n">some_conn_id</span><span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后,你记录在适配器中的任何事件消息前将添加 <code class="docutils literal notranslate"><span class="pre">some_conn_id</span></code> 的值。</p>
|
||
<section id="using-objects-other-than-dicts-to-pass-contextual-information">
|
||
<h4>使用除字典之外的其它对象传递上下文信息<a class="headerlink" href="#using-objects-other-than-dicts-to-pass-contextual-information" title="永久链接至标题">¶</a></h4>
|
||
<p>你不需要将一个实际的字典传递给 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code>-你可以传入一个实现了 <code class="docutils literal notranslate"><span class="pre">__getitem__</span></code> 和 <code class="docutils literal notranslate"><span class="pre">__iter__</span></code> 的类的实例,这样它就像是一个字典。这对于你想动态生成值(而字典中的值往往是常量)将很有帮助。</p>
|
||
</section>
|
||
</section>
|
||
<section id="using-filters-to-impart-contextual-information">
|
||
<span id="filters-contextual"></span><h3>使用过滤器传递上下文信息<a class="headerlink" href="#using-filters-to-impart-contextual-information" title="永久链接至标题">¶</a></h3>
|
||
<p>你也可以使用一个用户定义的类 <code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code> 在日志输出中添加上下文信息。<code class="docutils literal notranslate"><span class="pre">Filter</span></code> 的实例是被允许修改传入的 <code class="docutils literal notranslate"><span class="pre">LogRecords</span></code>,包括添加其他的属性,然后可以使用合适的格式化字符串输出,或者可以使用一个自定义的类 <code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code>。</p>
|
||
<p>例如,在一个web应用程序中,正在处理的请求(或者至少是请求的一部分),可以存储在一个线程本地 (<a class="reference internal" href="../library/threading.html#threading.local" title="threading.local"><code class="xref py py-class docutils literal notranslate"><span class="pre">threading.local</span></code></a>) 变量中,然后从 <code class="docutils literal notranslate"><span class="pre">Filter</span></code> 中去访问。请求中的信息,如IP地址和用户名将被存储在 <code class="docutils literal notranslate"><span class="pre">LogRecord</span></code> 中,使用上例 <code class="docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 中的 'ip' 和 'user' 属性名。在这种情况下,可以使用相同的格式化字符串来得到上例中类似的输出结果。这是一段示例代码:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">from</span> <span class="nn">random</span> <span class="kn">import</span> <span class="n">choice</span>
|
||
|
||
<span class="k">class</span> <span class="nc">ContextFilter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Filter</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> This is a filter which injects contextual information into the log.</span>
|
||
|
||
<span class="sd"> Rather than use actual contextual information, we just use random</span>
|
||
<span class="sd"> data in this demo.</span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="n">USERS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'jim'</span><span class="p">,</span> <span class="s1">'fred'</span><span class="p">,</span> <span class="s1">'sheila'</span><span class="p">]</span>
|
||
<span class="n">IPS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'123.231.231.123'</span><span class="p">,</span> <span class="s1">'127.0.0.1'</span><span class="p">,</span> <span class="s1">'192.168.0.1'</span><span class="p">]</span>
|
||
|
||
<span class="k">def</span> <span class="nf">filter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
|
||
<span class="n">record</span><span class="o">.</span><span class="n">ip</span> <span class="o">=</span> <span class="n">choice</span><span class="p">(</span><span class="n">ContextFilter</span><span class="o">.</span><span class="n">IPS</span><span class="p">)</span>
|
||
<span class="n">record</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="n">choice</span><span class="p">(</span><span class="n">ContextFilter</span><span class="o">.</span><span class="n">USERS</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="kc">True</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">levels</span> <span class="o">=</span> <span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span>
|
||
<span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(asctime)-15s</span><span class="s1"> </span><span class="si">%(name)-5s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> IP: </span><span class="si">%(ip)-15s</span><span class="s1"> User: </span><span class="si">%(user)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">a1</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'a.b.c'</span><span class="p">)</span>
|
||
<span class="n">a2</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'d.e.f'</span><span class="p">)</span>
|
||
|
||
<span class="n">f</span> <span class="o">=</span> <span class="n">ContextFilter</span><span class="p">()</span>
|
||
<span class="n">a1</span><span class="o">.</span><span class="n">addFilter</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="n">a2</span><span class="o">.</span><span class="n">addFilter</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="n">a1</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'A debug message'</span><span class="p">)</span>
|
||
<span class="n">a1</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'An info message with </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'some parameters'</span><span class="p">)</span>
|
||
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||
<span class="n">lvl</span> <span class="o">=</span> <span class="n">choice</span><span class="p">(</span><span class="n">levels</span><span class="p">)</span>
|
||
<span class="n">lvlname</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLevelName</span><span class="p">(</span><span class="n">lvl</span><span class="p">)</span>
|
||
<span class="n">a2</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">lvl</span><span class="p">,</span> <span class="s1">'A message at </span><span class="si">%s</span><span class="s1"> level with </span><span class="si">%d</span><span class="s1"> </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">lvlname</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'parameters'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>在运行时,产生如下内容:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>2010-09-06 22:38:15,292 a.b.c DEBUG IP: 123.231.231.123 User: fred A debug message
|
||
2010-09-06 22:38:15,300 a.b.c INFO IP: 192.168.0.1 User: sheila An info message with some parameters
|
||
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f ERROR IP: 127.0.0.1 User: jim A message at ERROR level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f DEBUG IP: 127.0.0.1 User: sheila A message at DEBUG level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f ERROR IP: 123.231.231.123 User: fred A message at ERROR level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL level with 2 parameters
|
||
2010-09-06 22:38:15,300 d.e.f DEBUG IP: 192.168.0.1 User: jim A message at DEBUG level with 2 parameters
|
||
2010-09-06 22:38:15,301 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters
|
||
2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters
|
||
2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="logging-to-a-single-file-from-multiple-processes">
|
||
<span id="multiple-processes"></span><h2>从多个进程记录至单个文件<a class="headerlink" href="#logging-to-a-single-file-from-multiple-processes" title="永久链接至标题">¶</a></h2>
|
||
<p>尽管 logging 是线程安全的,将单个进程中的多个线程日志记录至单个文件也 <em>是</em> 受支持的,但将 <em>多个进程</em> 中的日志记录至单个文件则 <em>不是</em> 受支持的,因为在 Python 中并没有在多个进程中实现对单个文件访问的序列化的标准方案。 如果你需要将多个进程中的日志记录至单个文件,有一个方案是让所有进程都将日志记录至一个 <code class="xref py py-class docutils literal notranslate"><span class="pre">SocketHandler</span></code>,然后用一个实现了套接字服务器的单独进程一边从套接字中读取一边将日志记录至文件。 (如果愿意的话,你可以在一个现有进程中专门开一个线程来执行此项功能。) <a class="reference internal" href="#network-logging"><span class="std std-ref">这一部分</span></a> 文档对此方式有更详细的介绍,并包含一个可用的套接字接收器,你自己的应用可以在此基础上进行适配。</p>
|
||
<p>你也可以编写你自己的处理程序,让其使用 <a class="reference internal" href="../library/multiprocessing.html#module-multiprocessing" title="multiprocessing: Process-based parallelism."><code class="xref py py-mod docutils literal notranslate"><span class="pre">multiprocessing</span></code></a> 模块中的 <a class="reference internal" href="../library/multiprocessing.html#multiprocessing.Lock" title="multiprocessing.Lock"><code class="xref py py-class docutils literal notranslate"><span class="pre">Lock</span></code></a> 类来顺序访问你的多个进程中的文件。 现有的 <code class="xref py py-class docutils literal notranslate"><span class="pre">FileHandler</span></code> 及其子类目前并不使用 <a class="reference internal" href="../library/multiprocessing.html#module-multiprocessing" title="multiprocessing: Process-based parallelism."><code class="xref py py-mod docutils literal notranslate"><span class="pre">multiprocessing</span></code></a>,尽管它们将来可能会这样做。 请注意在目前,<a class="reference internal" href="../library/multiprocessing.html#module-multiprocessing" title="multiprocessing: Process-based parallelism."><code class="xref py py-mod docutils literal notranslate"><span class="pre">multiprocessing</span></code></a> 模块并未在所有平台上都提供可用的锁功能 (参见 <a class="reference external" href="https://bugs.python.org/issue3770">https://bugs.python.org/issue3770</a>)。</p>
|
||
<p>或者,你也可以使用 <code class="docutils literal notranslate"><span class="pre">Queue</span></code> 和 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.QueueHandler" title="logging.handlers.QueueHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">QueueHandler</span></code></a> 将所有的日志事件发送至你的多进程应用的一个进程中。 以下示例脚本演示了如何执行此操作。 在示例中,一个单独的监听进程负责监听其他进程的日志事件,并根据自己的配置记录。 尽管示例只演示了这种方法(例如你可能希望使用单独的监听线程而非监听进程 —— 它们的实现是类似的),但你也可以在应用程序的监听进程和其他进程使用不同的配置,它可以作为满足你特定需求的一个基础:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># You'll need these imports in your own code</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.handlers</span>
|
||
<span class="kn">import</span> <span class="nn">multiprocessing</span>
|
||
|
||
<span class="c1"># Next two import lines for this demo only</span>
|
||
<span class="kn">from</span> <span class="nn">random</span> <span class="kn">import</span> <span class="n">choice</span><span class="p">,</span> <span class="n">random</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Because you'll want to define the logging configurations for listener and workers, the</span>
|
||
<span class="c1"># listener and worker process functions take a configurer parameter which is a callable</span>
|
||
<span class="c1"># for configuring logging for that process. These functions are also passed the queue,</span>
|
||
<span class="c1"># which they use for communication.</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># In practice, you can configure the listener however you want, but note that in this</span>
|
||
<span class="c1"># simple example, the listener does not apply level or filter logic to received records.</span>
|
||
<span class="c1"># In practice, you would probably want to do this logic in the worker processes, to avoid</span>
|
||
<span class="c1"># sending events which would be filtered out between processes.</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># The size of the rotated files is made small so you can see the results easily.</span>
|
||
<span class="k">def</span> <span class="nf">listener_configurer</span><span class="p">():</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">h</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">RotatingFileHandler</span><span class="p">(</span><span class="s1">'mptest.log'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(processName)-10s</span><span class="s1"> </span><span class="si">%(name)s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">h</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
|
||
|
||
<span class="c1"># This is the listener process top-level loop: wait for logging events</span>
|
||
<span class="c1"># (LogRecords)on the queue and handle them, quit when you get a None for a</span>
|
||
<span class="c1"># LogRecord.</span>
|
||
<span class="k">def</span> <span class="nf">listener_process</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">configurer</span><span class="p">):</span>
|
||
<span class="n">configurer</span><span class="p">()</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">record</span> <span class="o">=</span> <span class="n">queue</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="n">record</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># We send this as a sentinel to tell the listener to quit.</span>
|
||
<span class="k">break</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">handle</span><span class="p">(</span><span class="n">record</span><span class="p">)</span> <span class="c1"># No level or filter logic applied - just do it!</span>
|
||
<span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">traceback</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'Whoops! Problem:'</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
|
||
<span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">(</span><span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Arrays used for random selections in this demo</span>
|
||
|
||
<span class="n">LEVELS</span> <span class="o">=</span> <span class="p">[</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">]</span>
|
||
|
||
<span class="n">LOGGERS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'a.b.c'</span><span class="p">,</span> <span class="s1">'d.e.f'</span><span class="p">]</span>
|
||
|
||
<span class="n">MESSAGES</span> <span class="o">=</span> <span class="p">[</span>
|
||
<span class="s1">'Random message #1'</span><span class="p">,</span>
|
||
<span class="s1">'Random message #2'</span><span class="p">,</span>
|
||
<span class="s1">'Random message #3'</span><span class="p">,</span>
|
||
<span class="p">]</span>
|
||
|
||
<span class="c1"># The worker configuration is done at the start of the worker process run.</span>
|
||
<span class="c1"># Note that on Windows you can't rely on fork semantics, so each process</span>
|
||
<span class="c1"># will run the logging configuration code when it starts.</span>
|
||
<span class="k">def</span> <span class="nf">worker_configurer</span><span class="p">(</span><span class="n">queue</span><span class="p">):</span>
|
||
<span class="n">h</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">QueueHandler</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span> <span class="c1"># Just the one handler needed</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
|
||
<span class="c1"># send all messages, for demo; no other level or filter logic applied.</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
|
||
<span class="c1"># This is the worker process top-level loop, which just logs ten events with</span>
|
||
<span class="c1"># random intervening delays before terminating.</span>
|
||
<span class="c1"># The print messages are just so you know it's doing something!</span>
|
||
<span class="k">def</span> <span class="nf">worker_process</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">configurer</span><span class="p">):</span>
|
||
<span class="n">configurer</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">current_process</span><span class="p">()</span><span class="o">.</span><span class="n">name</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'Worker started: </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">random</span><span class="p">())</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">choice</span><span class="p">(</span><span class="n">LOGGERS</span><span class="p">))</span>
|
||
<span class="n">level</span> <span class="o">=</span> <span class="n">choice</span><span class="p">(</span><span class="n">LEVELS</span><span class="p">)</span>
|
||
<span class="n">message</span> <span class="o">=</span> <span class="n">choice</span><span class="p">(</span><span class="n">MESSAGES</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'Worker finished: </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Here's where the demo gets orchestrated. Create the queue, create and start</span>
|
||
<span class="c1"># the listener, create ten workers and start them, wait for them to finish,</span>
|
||
<span class="c1"># then send a None to the queue to tell the listener to finish.</span>
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">queue</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Queue</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="n">listener</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">listener_process</span><span class="p">,</span>
|
||
<span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">listener_configurer</span><span class="p">))</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="n">workers</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||
<span class="n">worker</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker_process</span><span class="p">,</span>
|
||
<span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">worker_configurer</span><span class="p">))</span>
|
||
<span class="n">workers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">worker</span><span class="p">)</span>
|
||
<span class="n">worker</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="k">for</span> <span class="n">w</span> <span class="ow">in</span> <span class="n">workers</span><span class="p">:</span>
|
||
<span class="n">w</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
<span class="n">queue</span><span class="o">.</span><span class="n">put_nowait</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>上面脚本的一个变种,仍然在主进程中记录日志,但使用一个单独的线程:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.config</span>
|
||
<span class="kn">import</span> <span class="nn">logging.handlers</span>
|
||
<span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</span><span class="p">,</span> <span class="n">Queue</span>
|
||
<span class="kn">import</span> <span class="nn">random</span>
|
||
<span class="kn">import</span> <span class="nn">threading</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="k">def</span> <span class="nf">logger_thread</span><span class="p">(</span><span class="n">q</span><span class="p">):</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">record</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="n">record</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="k">break</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">handle</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
|
||
|
||
<span class="k">def</span> <span class="nf">worker_process</span><span class="p">(</span><span class="n">q</span><span class="p">):</span>
|
||
<span class="n">qh</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">QueueHandler</span><span class="p">(</span><span class="n">q</span><span class="p">)</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">qh</span><span class="p">)</span>
|
||
<span class="n">levels</span> <span class="o">=</span> <span class="p">[</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">]</span>
|
||
<span class="n">loggers</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'foo'</span><span class="p">,</span> <span class="s1">'foo.bar'</span><span class="p">,</span> <span class="s1">'foo.bar.baz'</span><span class="p">,</span>
|
||
<span class="s1">'spam'</span><span class="p">,</span> <span class="s1">'spam.ham'</span><span class="p">,</span> <span class="s1">'spam.ham.eggs'</span><span class="p">]</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
|
||
<span class="n">lvl</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">levels</span><span class="p">)</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">loggers</span><span class="p">))</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">lvl</span><span class="p">,</span> <span class="s1">'Message no. </span><span class="si">%d</span><span class="s1">'</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">q</span> <span class="o">=</span> <span class="n">Queue</span><span class="p">()</span>
|
||
<span class="n">d</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'detailed'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.Formatter'</span><span class="p">,</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(name)-15s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(processName)-10s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'console'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'INFO'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'file'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'foofile'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog-foo.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'errors'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog-errors.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'ERROR'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'loggers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'foo'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'foofile'</span><span class="p">]</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console'</span><span class="p">,</span> <span class="s1">'file'</span><span class="p">,</span> <span class="s1">'errors'</span><span class="p">]</span>
|
||
<span class="p">},</span>
|
||
<span class="p">}</span>
|
||
<span class="n">workers</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
|
||
<span class="n">wp</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker_process</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'worker </span><span class="si">%d</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">q</span><span class="p">,))</span>
|
||
<span class="n">workers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">wp</span><span class="p">)</span>
|
||
<span class="n">wp</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
|
||
<span class="n">lp</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">logger_thread</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">q</span><span class="p">,))</span>
|
||
<span class="n">lp</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="c1"># At this point, the main process could do some useful work of its own</span>
|
||
<span class="c1"># Once it's done that, it can wait for the workers to terminate...</span>
|
||
<span class="k">for</span> <span class="n">wp</span> <span class="ow">in</span> <span class="n">workers</span><span class="p">:</span>
|
||
<span class="n">wp</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
<span class="c1"># And now tell the logging thread to finish up, too</span>
|
||
<span class="n">q</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="n">lp</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>这段变种的代码展示了如何使用特定的日志记录配置 - 例如 <code class="docutils literal notranslate"><span class="pre">foo</span></code> 记录器使用了特殊的处理程序,将 <code class="docutils literal notranslate"><span class="pre">foo</span></code> 子系统中所有的事件记录至一个文件 <code class="docutils literal notranslate"><span class="pre">mplog-foo.log</span></code> 。在主进程(即使是在工作进程中产生的日志事件)的日志记录机制中将直接使用恰当的配置。</p>
|
||
<section id="using-concurrent-futures-processpoolexecutor">
|
||
<h3>concurrent.futures.ProcessPoolExecutor 的用法<a class="headerlink" href="#using-concurrent-futures-processpoolexecutor" title="永久链接至标题">¶</a></h3>
|
||
<p>若要利用 <a class="reference internal" href="../library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor" title="concurrent.futures.ProcessPoolExecutor"><code class="xref py py-class docutils literal notranslate"><span class="pre">concurrent.futures.ProcessPoolExecutor</span></code></a> 启动工作进程,创建队列的方式应稍有不同。不能是:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">queue</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Queue</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>而应是:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">queue</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Manager</span><span class="p">()</span><span class="o">.</span><span class="n">Queue</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># also works with the examples above</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后就可以将以下工作进程的创建过程:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">workers</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||
<span class="n">worker</span> <span class="o">=</span> <span class="n">multiprocessing</span><span class="o">.</span><span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker_process</span><span class="p">,</span>
|
||
<span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">worker_configurer</span><span class="p">))</span>
|
||
<span class="n">workers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">worker</span><span class="p">)</span>
|
||
<span class="n">worker</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="k">for</span> <span class="n">w</span> <span class="ow">in</span> <span class="n">workers</span><span class="p">:</span>
|
||
<span class="n">w</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>改为 (记得要先导入 <a class="reference internal" href="../library/concurrent.futures.html#module-concurrent.futures" title="concurrent.futures: Execute computations concurrently using threads or processes."><code class="xref py py-mod docutils literal notranslate"><span class="pre">concurrent.futures</span></code></a>):</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">with</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||
<span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">worker_process</span><span class="p">,</span> <span class="n">queue</span><span class="p">,</span> <span class="n">worker_configurer</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="using-file-rotation">
|
||
<h2>轮换日志文件<a class="headerlink" href="#using-file-rotation" title="永久链接至标题">¶</a></h2>
|
||
<p>有时,你希望当日志文件不断记录增长至一定大小时,打开一个新的文件接着记录。 你可能希望只保留一定数量的日志文件,当不断的创建文件到达该数量时,又覆盖掉最开始的文件形成循环。 对于这种使用场景,日志包提供了 <code class="xref py py-class docutils literal notranslate"><span class="pre">RotatingFileHandler</span></code>:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">glob</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.handlers</span>
|
||
|
||
<span class="n">LOG_FILENAME</span> <span class="o">=</span> <span class="s1">'logging_rotatingfile_example.out'</span>
|
||
|
||
<span class="c1"># Set up a specific logger with our desired output level</span>
|
||
<span class="n">my_logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'MyLogger'</span><span class="p">)</span>
|
||
<span class="n">my_logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Add the log message handler to the logger</span>
|
||
<span class="n">handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">RotatingFileHandler</span><span class="p">(</span>
|
||
<span class="n">LOG_FILENAME</span><span class="p">,</span> <span class="n">maxBytes</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">backupCount</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
|
||
|
||
<span class="n">my_logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Log some messages</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
|
||
<span class="n">my_logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'i = </span><span class="si">%d</span><span class="s1">'</span> <span class="o">%</span> <span class="n">i</span><span class="p">)</span>
|
||
|
||
<span class="c1"># See what files are created</span>
|
||
<span class="n">logfiles</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s1">'</span><span class="si">%s</span><span class="s1">*'</span> <span class="o">%</span> <span class="n">LOG_FILENAME</span><span class="p">)</span>
|
||
|
||
<span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">logfiles</span><span class="p">:</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>结果应该是6个单独的文件,每个文件都包含了应用程序的部分历史日志:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>logging_rotatingfile_example.out
|
||
logging_rotatingfile_example.out.1
|
||
logging_rotatingfile_example.out.2
|
||
logging_rotatingfile_example.out.3
|
||
logging_rotatingfile_example.out.4
|
||
logging_rotatingfile_example.out.5
|
||
</pre></div>
|
||
</div>
|
||
<p>最新的文件始终是 <code class="file docutils literal notranslate"><span class="pre">logging_rotatingfile_example.out</span></code>,每次到达大小限制时,都会使用后缀 <code class="docutils literal notranslate"><span class="pre">.1</span></code> 重命名。每个现有的备份文件都会被重命名并增加其后缀(例如 <code class="docutils literal notranslate"><span class="pre">.1</span></code> 变为 <code class="docutils literal notranslate"><span class="pre">.2</span></code> ),而 <code class="docutils literal notranslate"><span class="pre">.6</span></code> 文件会被删除掉。</p>
|
||
<p>显然,这个例子将日志长度设置得太小,这是一个极端的例子。 你可能希望将 <em>maxBytes</em> 设置为一个合适的值。</p>
|
||
</section>
|
||
<section id="use-of-alternative-formatting-styles">
|
||
<span id="format-styles"></span><h2>使用其他日志格式化方式<a class="headerlink" href="#use-of-alternative-formatting-styles" title="永久链接至标题">¶</a></h2>
|
||
<p>当日志模块被添加至 Python 标准库时,只有一种格式化消息内容的方法即 %-formatting。 在那之后,Python 又增加了两种格式化方法: <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a> (在 Python 2.4 中新增) 和 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> (在 Python 2.6 中新增)。</p>
|
||
<p>日志(从 3.2 开始)为这两种格式化方式提供了更多支持。<code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code> 类可以添加一个额外的可选关键字参数 <code class="docutils literal notranslate"><span class="pre">style</span></code>。它的默认值是 <code class="docutils literal notranslate"><span class="pre">'%'</span></code>,其他的值 <code class="docutils literal notranslate"><span class="pre">'{'</span></code> 和 <code class="docutils literal notranslate"><span class="pre">'$'</span></code> 也支持,对应了其他两种格式化样式。其保持了向后兼容(如您所愿),但通过显示指定样式参数,你可以指定格式化字符串的方式是使用 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> 或 <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a>。 这里是一个控制台会话的示例,展示了这些方式:</p>
|
||
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="gp">>>> </span><span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">root</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">bf</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'</span><span class="si">{asctime}</span><span class="s1"> </span><span class="si">{name}</span><span class="s1"> </span><span class="si">{levelname:8s}</span><span class="s1"> </span><span class="si">{message}</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="gp">... </span> <span class="n">style</span><span class="o">=</span><span class="s1">'{'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">handler</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">bf</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'foo.bar'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'This is a DEBUG message'</span><span class="p">)</span>
|
||
<span class="go">2010-10-28 15:11:55,341 foo.bar DEBUG This is a DEBUG message</span>
|
||
<span class="gp">>>> </span><span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'This is a CRITICAL message'</span><span class="p">)</span>
|
||
<span class="go">2010-10-28 15:12:11,526 foo.bar CRITICAL This is a CRITICAL message</span>
|
||
<span class="gp">>>> </span><span class="n">df</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="s1">'$asctime $name $</span><span class="si">{levelname}</span><span class="s1"> $message'</span><span class="p">,</span>
|
||
<span class="gp">... </span> <span class="n">style</span><span class="o">=</span><span class="s1">'$'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">handler</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">df</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'This is a DEBUG message'</span><span class="p">)</span>
|
||
<span class="go">2010-10-28 15:13:06,924 foo.bar DEBUG This is a DEBUG message</span>
|
||
<span class="gp">>>> </span><span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'This is a CRITICAL message'</span><span class="p">)</span>
|
||
<span class="go">2010-10-28 15:13:11,494 foo.bar CRITICAL This is a CRITICAL message</span>
|
||
<span class="gp">>>></span>
|
||
</pre></div>
|
||
</div>
|
||
<p>请注意最终输出到日志的消息格式完全独立于单条日志消息的构造方式。 它仍然可以使用 %-formatting,如下所示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'This is an</span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'other,'</span><span class="p">,</span> <span class="s1">'ERROR,'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">)</span>
|
||
<span class="go">2010-10-28 15:19:29,833 foo.bar ERROR This is another, ERROR, message</span>
|
||
<span class="gp">>>></span>
|
||
</pre></div>
|
||
</div>
|
||
<p>日志调用(<code class="docutils literal notranslate"><span class="pre">logger.debug()</span></code> 、<code class="docutils literal notranslate"><span class="pre">logger.info()</span></code> 等)接受的位置参数只会用于日志信息本身,而关键字参数仅用于日志调用的可选处理参数(如关键字参数 <code class="docutils literal notranslate"><span class="pre">exc_info</span></code> 表示应记录跟踪信息, <code class="docutils literal notranslate"><span class="pre">extra</span></code> 则标识了需要加入日志的额外上下文信息)。所以不能直接用 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> 或 <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a> 语法进行日志调用,因为日志包在内部使用 %-f 格式来合并格式串和参数变量。在保持向下兼容性时,这一点不会改变,因为已有代码中的所有日志调用都会使用%-f 格式串。</p>
|
||
<p>还有一种方法可以构建自己的日志信息,就是利用 {}- 和 $- 格式。回想一下,任意对象都可用为日志信息的格式串,日志包将会调用该对象的 <code class="docutils literal notranslate"><span class="pre">str()</span></code> 方法,以获取最终的格式串。不妨看下一下两个类:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">BraceMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fmt</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">fmt</span> <span class="o">=</span> <span class="n">fmt</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">args</span> <span class="o">=</span> <span class="n">args</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">fmt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">DollarMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fmt</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">fmt</span> <span class="o">=</span> <span class="n">fmt</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="kn">from</span> <span class="nn">string</span> <span class="kn">import</span> <span class="n">Template</span>
|
||
<span class="k">return</span> <span class="n">Template</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">fmt</span><span class="p">)</span><span class="o">.</span><span class="n">substitute</span><span class="p">(</span><span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>上述两个类均可代替格式串,使得能用 {}- 或 $-formatting 构建最终的“日志信息”部分,这些信息将出现在格式化后的日志输出中,替换 %(message)s 或“{message}”或“$message”。每次写入日志时都要使用类名,有点不大实用,但如果用上 __ 之类的别名就相当合适了(双下划线 --- 不要与 _ 混淆,单下划线用作 <a class="reference internal" href="../library/gettext.html#gettext.gettext" title="gettext.gettext"><code class="xref py py-func docutils literal notranslate"><span class="pre">gettext.gettext()</span></code></a> 或相关函数的同义词/别名 )。</p>
|
||
<p>Python 并没有上述两个类,当然复制粘贴到自己的代码中也很容易。用法可如下所示(假定在名为 <code class="docutils literal notranslate"><span class="pre">wherever</span></code> 的模块中声明):</p>
|
||
<div class="highlight-pycon notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">wherever</span> <span class="kn">import</span> <span class="n">BraceMessage</span> <span class="k">as</span> <span class="n">__</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with </span><span class="si">{0}</span><span class="s1"> </span><span class="si">{name}</span><span class="s1">'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'placeholders'</span><span class="p">))</span>
|
||
<span class="go">Message with 2 placeholders</span>
|
||
<span class="gp">>>> </span><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">p</span> <span class="o">=</span> <span class="n">Point</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">p</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
<span class="gp">>>> </span><span class="n">p</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with coordinates: (</span><span class="si">{point.x:.2f}</span><span class="s1">, </span><span class="si">{point.y:.2f}</span><span class="s1">)'</span><span class="p">,</span>
|
||
<span class="gp">... </span> <span class="n">point</span><span class="o">=</span><span class="n">p</span><span class="p">))</span>
|
||
<span class="go">Message with coordinates: (0.50, 0.50)</span>
|
||
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">wherever</span> <span class="kn">import</span> <span class="n">DollarMessage</span> <span class="k">as</span> <span class="n">__</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with $num $what'</span><span class="p">,</span> <span class="n">num</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">what</span><span class="o">=</span><span class="s1">'placeholders'</span><span class="p">))</span>
|
||
<span class="go">Message with 2 placeholders</span>
|
||
<span class="gp">>>></span>
|
||
</pre></div>
|
||
</div>
|
||
<p>上述示例用了 <code class="docutils literal notranslate"><span class="pre">print()</span></code> 演示格式化输出的过程,实际记录日志时当然会用类似 <code class="docutils literal notranslate"><span class="pre">logger.debug()</span></code> 的方法来应用。</p>
|
||
<p>值得注意的是,上述做法对性能并没什么影响:格式化过程其实不是在日志记录调用时发生的,而是在日志信息即将由 handler 输出到日志时发生。因此,唯一可能让人困惑的稍不寻常的地方,就是包裹在格式串和参数外面的括号,而不是格式串。因为 __ 符号只是对 XXXMessage 类的构造函数调用的语法糖。</p>
|
||
<p>只要愿意,上述类似的效果即可用 <code class="xref py py-class docutils literal notranslate"><span class="pre">LoggerAdapter</span></code> 实现,如下例所示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="k">class</span> <span class="nc">Message</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fmt</span><span class="p">,</span> <span class="n">args</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">fmt</span> <span class="o">=</span> <span class="n">fmt</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">args</span> <span class="o">=</span> <span class="n">args</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">fmt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">StyleAdapter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">LoggerAdapter</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">logger</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">logger</span><span class="p">,</span> <span class="n">extra</span> <span class="ow">or</span> <span class="p">{})</span>
|
||
|
||
<span class="k">def</span> <span class="nf">log</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">level</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">isEnabledFor</span><span class="p">(</span><span class="n">level</span><span class="p">):</span>
|
||
<span class="n">msg</span><span class="p">,</span> <span class="n">kwargs</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">process</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">_log</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="n">Message</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">args</span><span class="p">),</span> <span class="p">(),</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">StyleAdapter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">))</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Hello, </span><span class="si">{}</span><span class="s1">'</span><span class="p">,</span> <span class="s1">'world!'</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>在用 Python 3.2 以上版本运行时,上述代码应该会把 <code class="docutils literal notranslate"><span class="pre">Hello,</span> <span class="pre">world!</span></code> 写入日志。</p>
|
||
</section>
|
||
<section id="customizing-logrecord">
|
||
<span id="custom-logrecord"></span><h2>自定义 <code class="docutils literal notranslate"><span class="pre">LogRecord</span></code><a class="headerlink" href="#customizing-logrecord" title="永久链接至标题">¶</a></h2>
|
||
<p>每条日志事件都由一个 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 实例表示。当某事件要记入日志并且没有被某级别过滤掉时,就会创建一个 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 对象,并将有关事件的信息填入,传给该日志对象的 handler(及其祖先,直至对象禁止向上传播为止)。在 Python 3.2 之前,只有两个地方会进行事件的创建:</p>
|
||
<ul class="simple">
|
||
<li><p><a class="reference internal" href="../library/logging.html#logging.Logger.makeRecord" title="logging.Logger.makeRecord"><code class="xref py py-meth docutils literal notranslate"><span class="pre">Logger.makeRecord()</span></code></a>,在事件正常记入日志的过程中调用。这会直接调用 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 来创建一个实例。</p></li>
|
||
<li><p><a class="reference internal" href="../library/logging.html#logging.makeLogRecord" title="logging.makeLogRecord"><code class="xref py py-func docutils literal notranslate"><span class="pre">makeLogRecord()</span></code></a>,调用时会带上一个字典参数,其中存放着要加入 LogRecord 的属性。这通常在通过网络接收到合适的字典时调用(如通过 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SocketHandler" title="logging.handlers.SocketHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SocketHandler</span></code></a> 以 pickle 形式,或通过 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.HTTPHandler" title="logging.handlers.HTTPHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">HTTPHandler</span></code></a> 以 JSON 形式)。</p></li>
|
||
</ul>
|
||
<p>于是这意味着若要对 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 进行定制,必须进行下述某种操作。</p>
|
||
<ul class="simple">
|
||
<li><p>创建 <a class="reference internal" href="../library/logging.html#logging.Logger" title="logging.Logger"><code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code></a> 自定义子类,重写 <a class="reference internal" href="../library/logging.html#logging.Logger.makeRecord" title="logging.Logger.makeRecord"><code class="xref py py-meth docutils literal notranslate"><span class="pre">Logger.makeRecord()</span></code></a>,并在实例化所需日志对象之前用 <a class="reference internal" href="../library/logging.html#logging.setLoggerClass" title="logging.setLoggerClass"><code class="xref py py-func docutils literal notranslate"><span class="pre">setLoggerClass()</span></code></a> 进行设置。</p></li>
|
||
<li><p>为日志对象添加 <a class="reference internal" href="../library/logging.html#logging.Filter" title="logging.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code></a> 或 handler,当其 <a class="reference internal" href="../library/logging.html#logging.Filter.filter" title="logging.Filter.filter"><code class="xref py py-meth docutils literal notranslate"><span class="pre">filter()</span></code></a> 方法被调用时,会执行必要的定制操作。</p></li>
|
||
</ul>
|
||
<p>比如说在有多个不同库要完成不同操作的场景下,第一种方式会有点笨拙。 每次都要尝试设置自己的 <a class="reference internal" href="../library/logging.html#logging.Logger" title="logging.Logger"><code class="xref py py-class docutils literal notranslate"><span class="pre">Logger</span></code></a> 子类,而起作用的是最后一次尝试。</p>
|
||
<p>第二种方式在多数情况下效果都比较良好,但不允许你使用特殊化的 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 子类。 库开发者可以为他们的日志记录器设置合适的过滤器,但他们应当要记得每次引入新的日志记录器时都需如此(他们只需通过添加新的包或模块并执行以下操作即可):</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>或许这样要顾及太多事情。开发人员还可以将过滤器附加到其顶级日志对象的 <a class="reference internal" href="../library/logging.handlers.html#logging.NullHandler" title="logging.NullHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">NullHandler</span></code></a> 中,但如果应用程序开发人员将 handler 附加到较底层库的日志对象,则不会调用该过滤器 --- 所以 handler 输出的内容不会符合库开发人员的预期。</p>
|
||
<p>在 Python 3.2 以上版本中,<a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 的创建是通过工厂对象完成的,工厂对象可以指定。工厂对象只是一个可调用对象,可以用 <a class="reference internal" href="../library/logging.html#logging.setLogRecordFactory" title="logging.setLogRecordFactory"><code class="xref py py-func docutils literal notranslate"><span class="pre">setLogRecordFactory()</span></code></a> 进行设置,并用 <a class="reference internal" href="../library/logging.html#logging.getLogRecordFactory" title="logging.getLogRecordFactory"><code class="xref py py-func docutils literal notranslate"><span class="pre">getLogRecordFactory()</span></code></a> 进行查询。工厂对象的调用参数与 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 的构造函数相同,因为 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 是工厂对象的默认设置。</p>
|
||
<p>这种方式可以让自定义工厂对象完全控制 LogRecord 的创建过程。比如可以返回一个子类,或者在创建的日志对象中加入一些额外的属性,使用方式如下所示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">old_factory</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogRecordFactory</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">record_factory</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="n">record</span> <span class="o">=</span> <span class="n">old_factory</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="n">record</span><span class="o">.</span><span class="n">custom_attribute</span> <span class="o">=</span> <span class="mh">0xdecafbad</span>
|
||
<span class="k">return</span> <span class="n">record</span>
|
||
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">setLogRecordFactory</span><span class="p">(</span><span class="n">record_factory</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>这种模式允许不同的库将多个工厂对象链在一起,只要不会覆盖彼此的属性或标准属性,就不会出现意外。但应记住,工厂链中的每个节点都会增加日志操作的运行开销,本技术仅在采用 <a class="reference internal" href="../library/logging.html#logging.Filter" title="logging.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code></a> 无法达到目标时才应使用。</p>
|
||
</section>
|
||
<section id="subclassing-queuehandler-a-zeromq-example">
|
||
<span id="zeromq-handlers"></span><h2>子类化 QueueHandler - ZeroMQ 示例<a class="headerlink" href="#subclassing-queuehandler-a-zeromq-example" title="永久链接至标题">¶</a></h2>
|
||
<p>你可以使用 <code class="xref py py-class docutils literal notranslate"><span class="pre">QueueHandler</span></code> 子类将消息发送给其他类型的队列 ,比如 ZeroMQ 'publish' 套接字。 在以下示例中,套接字将单独创建并传给处理器 (作为它的 'queue'):</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">zmq</span> <span class="c1"># using pyzmq, the Python binding for ZeroMQ</span>
|
||
<span class="kn">import</span> <span class="nn">json</span> <span class="c1"># for serializing records portably</span>
|
||
|
||
<span class="n">ctx</span> <span class="o">=</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Context</span><span class="p">()</span>
|
||
<span class="n">sock</span> <span class="o">=</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Socket</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">zmq</span><span class="o">.</span><span class="n">PUB</span><span class="p">)</span> <span class="c1"># or zmq.PUSH, or other suitable value</span>
|
||
<span class="n">sock</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="s1">'tcp://*:5556'</span><span class="p">)</span> <span class="c1"># or wherever</span>
|
||
|
||
<span class="k">class</span> <span class="nc">ZeroMQSocketHandler</span><span class="p">(</span><span class="n">QueueHandler</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="nf">enqueue</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">queue</span><span class="o">.</span><span class="n">send_json</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span>
|
||
|
||
|
||
<span class="n">handler</span> <span class="o">=</span> <span class="n">ZeroMQSocketHandler</span><span class="p">(</span><span class="n">sock</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>当然还有其他方案,比如通过 hander 传入所需数据,以创建 socket:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ZeroMQSocketHandler</span><span class="p">(</span><span class="n">QueueHandler</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uri</span><span class="p">,</span> <span class="n">socktype</span><span class="o">=</span><span class="n">zmq</span><span class="o">.</span><span class="n">PUB</span><span class="p">,</span> <span class="n">ctx</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">ctx</span> <span class="o">=</span> <span class="n">ctx</span> <span class="ow">or</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Context</span><span class="p">()</span>
|
||
<span class="n">socket</span> <span class="o">=</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Socket</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ctx</span><span class="p">,</span> <span class="n">socktype</span><span class="p">)</span>
|
||
<span class="n">socket</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">enqueue</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">queue</span><span class="o">.</span><span class="n">send_json</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">close</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">queue</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="subclassing-queuelistener-a-zeromq-example">
|
||
<h2>子类化 QueueListener —— ZeroMQ 示例<a class="headerlink" href="#subclassing-queuelistener-a-zeromq-example" title="永久链接至标题">¶</a></h2>
|
||
<p>你还可以子类化 <code class="xref py py-class docutils literal notranslate"><span class="pre">QueueListener</span></code> 来从其他类型的队列中获取消息,比如从 ZeroMQ 'subscribe' 套接字。 下面是一个例子:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ZeroMQSocketListener</span><span class="p">(</span><span class="n">QueueListener</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uri</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">*</span><span class="n">handlers</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">ctx</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'ctx'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Context</span><span class="p">()</span>
|
||
<span class="n">socket</span> <span class="o">=</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Socket</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ctx</span><span class="p">,</span> <span class="n">zmq</span><span class="o">.</span><span class="n">SUB</span><span class="p">)</span>
|
||
<span class="n">socket</span><span class="o">.</span><span class="n">setsockopt_string</span><span class="p">(</span><span class="n">zmq</span><span class="o">.</span><span class="n">SUBSCRIBE</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span> <span class="c1"># subscribe to everything</span>
|
||
<span class="n">socket</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="o">*</span><span class="n">handlers</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">dequeue</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">msg</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">queue</span><span class="o">.</span><span class="n">recv_json</span><span class="p">()</span>
|
||
<span class="k">return</span> <span class="n">logging</span><span class="o">.</span><span class="n">makeLogRecord</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition seealso">
|
||
<p class="admonition-title">参见</p>
|
||
<dl class="simple">
|
||
<dt>模块 <a class="reference internal" href="../library/logging.html#module-logging" title="logging: Flexible event logging system for applications."><code class="xref py py-mod docutils literal notranslate"><span class="pre">logging</span></code></a></dt><dd><p>日志记录模块的 API 参考。</p>
|
||
</dd>
|
||
<dt><a class="reference internal" href="../library/logging.config.html#module-logging.config" title="logging.config: Configuration of the logging module."><code class="xref py py-mod docutils literal notranslate"><span class="pre">logging.config</span></code></a> 模块</dt><dd><p>日志记录模块的配置 API 。</p>
|
||
</dd>
|
||
<dt><a class="reference internal" href="../library/logging.handlers.html#module-logging.handlers" title="logging.handlers: Handlers for the logging module."><code class="xref py py-mod docutils literal notranslate"><span class="pre">logging.handlers</span></code></a> 模块</dt><dd><p>日志记录模块附带的有用处理器。</p>
|
||
</dd>
|
||
</dl>
|
||
<p><a class="reference internal" href="logging.html#logging-basic-tutorial"><span class="std std-ref">日志操作基础教程</span></a></p>
|
||
<p><a class="reference internal" href="logging.html#logging-advanced-tutorial"><span class="std std-ref">日志操作的高级教程</span></a></p>
|
||
</div>
|
||
</section>
|
||
<section id="an-example-dictionary-based-configuration">
|
||
<h2>基于字典进行日志配置的示例<a class="headerlink" href="#an-example-dictionary-based-configuration" title="永久链接至标题">¶</a></h2>
|
||
<p>以下是日志配置字典的一个示例——它取自 Django 项目的`文档<<a class="reference external" href="https://docs.djangoproject.com/en/stable/topics/logging/#configuring-logging">https://docs.djangoproject.com/en/stable/topics/logging/#configuring-logging</a>>`_。此字典将被传给 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 以使配置生效:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'verbose'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(levelname)s</span><span class="s1"> </span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(module)s</span><span class="s1"> </span><span class="si">%(process)d</span><span class="s1"> </span><span class="si">%(thread)d</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'simple'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(levelname)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'filters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'special'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'()'</span><span class="p">:</span> <span class="s1">'project.logging.SpecialFilter'</span><span class="p">,</span>
|
||
<span class="s1">'foo'</span><span class="p">:</span> <span class="s1">'bar'</span><span class="p">,</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'null'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'level'</span><span class="p">:</span><span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'class'</span><span class="p">:</span><span class="s1">'django.utils.log.NullHandler'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'console'</span><span class="p">:{</span>
|
||
<span class="s1">'level'</span><span class="p">:</span><span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'class'</span><span class="p">:</span><span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'simple'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'mail_admins'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'ERROR'</span><span class="p">,</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'django.utils.log.AdminEmailHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filters'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'special'</span><span class="p">]</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'loggers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'django'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:[</span><span class="s1">'null'</span><span class="p">],</span>
|
||
<span class="s1">'propagate'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span><span class="s1">'INFO'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'django.request'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'mail_admins'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'ERROR'</span><span class="p">,</span>
|
||
<span class="s1">'propagate'</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'myproject.custom'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console'</span><span class="p">,</span> <span class="s1">'mail_admins'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'INFO'</span><span class="p">,</span>
|
||
<span class="s1">'filters'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'special'</span><span class="p">]</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>有关本配置的更多信息,请参阅 Django 文档的 <a class="reference external" href="https://docs.djangoproject.com/en/stable/topics/logging/#configuring-logging">有关章节</a> 。</p>
|
||
</section>
|
||
<section id="using-a-rotator-and-namer-to-customize-log-rotation-processing">
|
||
<span id="cookbook-rotator-namer"></span><h2>利用 rotator 和 namer 自定义日志轮换操作<a class="headerlink" href="#using-a-rotator-and-namer-to-customize-log-rotation-processing" title="永久链接至标题">¶</a></h2>
|
||
<p>以下代码给出了定义 namer 和 rotator 的示例,其中演示了基于 zlib 的日志文件压缩过程:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">namer</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">name</span> <span class="o">+</span> <span class="s2">".gz"</span>
|
||
|
||
<span class="k">def</span> <span class="nf">rotator</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">dest</span><span class="p">):</span>
|
||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="s2">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">sf</span><span class="p">:</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="n">sf</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
|
||
<span class="n">compressed</span> <span class="o">=</span> <span class="n">zlib</span><span class="o">.</span><span class="n">compress</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
|
||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="s2">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">df</span><span class="p">:</span>
|
||
<span class="n">df</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">compressed</span><span class="p">)</span>
|
||
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">source</span><span class="p">)</span>
|
||
|
||
<span class="n">rh</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">RotatingFileHandler</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
|
||
<span class="n">rh</span><span class="o">.</span><span class="n">rotator</span> <span class="o">=</span> <span class="n">rotator</span>
|
||
<span class="n">rh</span><span class="o">.</span><span class="n">namer</span> <span class="o">=</span> <span class="n">namer</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>这些不是“真正的” .gz 文件,因为他们只是纯压缩数据,缺少真正 gzip 文件中的“容器”。此段代码只是用于演示。</p>
|
||
</section>
|
||
<section id="a-more-elaborate-multiprocessing-example">
|
||
<h2>更加详细的多道处理示例<a class="headerlink" href="#a-more-elaborate-multiprocessing-example" title="永久链接至标题">¶</a></h2>
|
||
<p>以下可运行的示例显示了如何利用配置文件在多进程中应用日志。这些配置相当简单,但足以说明如何在真实的多进程场景中实现较为复杂的配置。</p>
|
||
<p>上述示例中,主进程产生一个侦听器进程和一些工作进程。每个主进程、侦听器进程和工作进程都有三种独立的日志配置(工作进程共享同一套配置)。大家可以看到主进程的日志记录过程、工作线程向 QueueHandler 写入日志的过程,以及侦听器实现 QueueListener 和较为复杂的日志配置,如何将由队列接收到的事件分发给配置指定的 handler。请注意,这些配置纯粹用于演示,但应该能调整代码以适用于自己的场景。</p>
|
||
<p>以下是代码——但愿文档字符串和注释能有助于理解其工作原理:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.config</span>
|
||
<span class="kn">import</span> <span class="nn">logging.handlers</span>
|
||
<span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</span><span class="p">,</span> <span class="n">Queue</span><span class="p">,</span> <span class="n">Event</span><span class="p">,</span> <span class="n">current_process</span>
|
||
<span class="kn">import</span> <span class="nn">os</span>
|
||
<span class="kn">import</span> <span class="nn">random</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="k">class</span> <span class="nc">MyHandler</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> A simple handler for logging events. It runs in the listener process and</span>
|
||
<span class="sd"> dispatches events to loggers based on the name in the received record,</span>
|
||
<span class="sd"> which then get dispatched, by the logging system, to the handlers</span>
|
||
<span class="sd"> configured for those loggers.</span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">record</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"root"</span><span class="p">:</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="n">logger</span><span class="o">.</span><span class="n">isEnabledFor</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="n">levelno</span><span class="p">):</span>
|
||
<span class="c1"># The process name is transformed just to show that it's the listener</span>
|
||
<span class="c1"># doing the logging to files and console</span>
|
||
<span class="n">record</span><span class="o">.</span><span class="n">processName</span> <span class="o">=</span> <span class="s1">'</span><span class="si">%s</span><span class="s1"> (for </span><span class="si">%s</span><span class="s1">)'</span> <span class="o">%</span> <span class="p">(</span><span class="n">current_process</span><span class="p">()</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">record</span><span class="o">.</span><span class="n">processName</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">handle</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">listener_process</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">stop_event</span><span class="p">,</span> <span class="n">config</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> This could be done in the main process, but is just done in a separate</span>
|
||
<span class="sd"> process for illustrative purposes.</span>
|
||
|
||
<span class="sd"> This initialises logging according to the specified configuration,</span>
|
||
<span class="sd"> starts the listener and waits for the main process to signal completion</span>
|
||
<span class="sd"> via the event. The listener is then stopped, and the process exits.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
|
||
<span class="n">listener</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">QueueListener</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">MyHandler</span><span class="p">())</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">'posix'</span><span class="p">:</span>
|
||
<span class="c1"># On POSIX, the setup logger will have been configured in the</span>
|
||
<span class="c1"># parent process, but should have been disabled following the</span>
|
||
<span class="c1"># dictConfig call.</span>
|
||
<span class="c1"># On Windows, since fork isn't used, the setup logger won't</span>
|
||
<span class="c1"># exist in the child, so it would be created and the message</span>
|
||
<span class="c1"># would appear - hence the "if posix" clause.</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'setup'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'Should not appear, because of disabled logger ...'</span><span class="p">)</span>
|
||
<span class="n">stop_event</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
|
||
<span class="n">listener</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">worker_process</span><span class="p">(</span><span class="n">config</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> A number of these are spawned for the purpose of illustration. In</span>
|
||
<span class="sd"> practice, they could be a heterogeneous bunch of processes rather than</span>
|
||
<span class="sd"> ones which are identical to each other.</span>
|
||
|
||
<span class="sd"> This initialises logging according to the specified configuration,</span>
|
||
<span class="sd"> and logs a hundred messages with random levels to randomly selected</span>
|
||
<span class="sd"> loggers.</span>
|
||
|
||
<span class="sd"> A small sleep is added to allow other processes a chance to run. This</span>
|
||
<span class="sd"> is not strictly needed, but it mixes the output from the different</span>
|
||
<span class="sd"> processes a bit more than if it's left out.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
|
||
<span class="n">levels</span> <span class="o">=</span> <span class="p">[</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">]</span>
|
||
<span class="n">loggers</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'foo'</span><span class="p">,</span> <span class="s1">'foo.bar'</span><span class="p">,</span> <span class="s1">'foo.bar.baz'</span><span class="p">,</span>
|
||
<span class="s1">'spam'</span><span class="p">,</span> <span class="s1">'spam.ham'</span><span class="p">,</span> <span class="s1">'spam.ham.eggs'</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">'posix'</span><span class="p">:</span>
|
||
<span class="c1"># On POSIX, the setup logger will have been configured in the</span>
|
||
<span class="c1"># parent process, but should have been disabled following the</span>
|
||
<span class="c1"># dictConfig call.</span>
|
||
<span class="c1"># On Windows, since fork isn't used, the setup logger won't</span>
|
||
<span class="c1"># exist in the child, so it would be created and the message</span>
|
||
<span class="c1"># would appear - hence the "if posix" clause.</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'setup'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'Should not appear, because of disabled logger ...'</span><span class="p">)</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
|
||
<span class="n">lvl</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">levels</span><span class="p">)</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">loggers</span><span class="p">))</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">lvl</span><span class="p">,</span> <span class="s1">'Message no. </span><span class="si">%d</span><span class="s1">'</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.01</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">q</span> <span class="o">=</span> <span class="n">Queue</span><span class="p">()</span>
|
||
<span class="c1"># The main process gets a simple configuration which prints to the console.</span>
|
||
<span class="n">config_initial</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'console'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'INFO'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
<span class="c1"># The worker process configuration is just a QueueHandler attached to the</span>
|
||
<span class="c1"># root logger, which allows all messages to be sent to the queue.</span>
|
||
<span class="c1"># We disable existing loggers to disable the "setup" logger used in the</span>
|
||
<span class="c1"># parent process. This is needed on POSIX because the logger will</span>
|
||
<span class="c1"># be there in the child following a fork().</span>
|
||
<span class="n">config_worker</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'queue'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.handlers.QueueHandler'</span><span class="p">,</span>
|
||
<span class="s1">'queue'</span><span class="p">:</span> <span class="n">q</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'queue'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
<span class="c1"># The listener process configuration shows that the full flexibility of</span>
|
||
<span class="c1"># logging configuration is available to dispatch events to handlers however</span>
|
||
<span class="c1"># you want.</span>
|
||
<span class="c1"># We disable existing loggers to disable the "setup" logger used in the</span>
|
||
<span class="c1"># parent process. This is needed on POSIX because the logger will</span>
|
||
<span class="c1"># be there in the child following a fork().</span>
|
||
<span class="n">config_listener</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'detailed'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.Formatter'</span><span class="p">,</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(name)-15s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(processName)-10s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'simple'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.Formatter'</span><span class="p">,</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(name)-15s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(processName)-10s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'console'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'simple'</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'INFO'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'file'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'foofile'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog-foo.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'errors'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.FileHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'mplog-errors.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'detailed'</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'ERROR'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'loggers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'foo'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'foofile'</span><span class="p">]</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console'</span><span class="p">,</span> <span class="s1">'file'</span><span class="p">,</span> <span class="s1">'errors'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
<span class="c1"># Log some initial events, just to show that logging in the parent works</span>
|
||
<span class="c1"># normally.</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">config_initial</span><span class="p">)</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'setup'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'About to create workers ...'</span><span class="p">)</span>
|
||
<span class="n">workers</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
|
||
<span class="n">wp</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker_process</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'worker </span><span class="si">%d</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span>
|
||
<span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">config_worker</span><span class="p">,))</span>
|
||
<span class="n">workers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">wp</span><span class="p">)</span>
|
||
<span class="n">wp</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Started worker: </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">wp</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'About to create listener ...'</span><span class="p">)</span>
|
||
<span class="n">stop_event</span> <span class="o">=</span> <span class="n">Event</span><span class="p">()</span>
|
||
<span class="n">lp</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">listener_process</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'listener'</span><span class="p">,</span>
|
||
<span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">stop_event</span><span class="p">,</span> <span class="n">config_listener</span><span class="p">))</span>
|
||
<span class="n">lp</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Started listener'</span><span class="p">)</span>
|
||
<span class="c1"># We now hang around for the workers to finish their work.</span>
|
||
<span class="k">for</span> <span class="n">wp</span> <span class="ow">in</span> <span class="n">workers</span><span class="p">:</span>
|
||
<span class="n">wp</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
<span class="c1"># Workers all done, listening can now stop.</span>
|
||
<span class="c1"># Logging in the parent still works normally.</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Telling listener to stop ...'</span><span class="p">)</span>
|
||
<span class="n">stop_event</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
|
||
<span class="n">lp</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'All done.'</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="inserting-a-bom-into-messages-sent-to-a-sysloghandler">
|
||
<h2>在发送给 SysLogHandler 的信息中插入一个 BOM。<a class="headerlink" href="#inserting-a-bom-into-messages-sent-to-a-sysloghandler" title="永久链接至标题">¶</a></h2>
|
||
<p><span class="target" id="index-8"></span><a class="rfc reference external" href="https://tools.ietf.org/html/rfc5424.html"><strong>RFC 5424</strong></a> 要求,Unicode 信息应采用字节流形式发送到系统 syslog 守护程序,字节流结构如下所示:可选的纯 ASCII部分,后跟 UTF-8 字节序标记(BOM),然后是采用 UTF-8 编码的 Unicode。(参见 <span class="target" id="index-9"></span><a class="rfc reference external" href="https://tools.ietf.org/html/rfc5424.html#section-6"><strong>相关规范</strong></a> 。)</p>
|
||
<p>在 Python 3.1 的 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SysLogHandler" title="logging.handlers.SysLogHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SysLogHandler</span></code></a> 中,已加入了在日志信息中插入 BOM 的代码,但不幸的是,代码并不正确,BOM 出现在了日志信息的开头,因此在它之前就不允许出现纯 ASCII 内容了。</p>
|
||
<p>由于无法正常工作, Python 3.2.4 以上版本已删除了出错的插入 BOM 代码。但已有版本的代码不会被替换,若要生成与 <span class="target" id="index-10"></span><a class="rfc reference external" href="https://tools.ietf.org/html/rfc5424.html"><strong>RFC 5424</strong></a> 兼容的日志信息,包括一个 BOM 符,前面有可选的纯 ASCII 字节流,后面为 UTF-8 编码的任意 Unicode,那么 需要执行以下操作:</p>
|
||
<ol class="arabic">
|
||
<li><p>为 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.SysLogHandler" title="logging.handlers.SysLogHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">SysLogHandler</span></code></a> 实例串上一个 <a class="reference internal" href="../library/logging.html#logging.Formatter" title="logging.Formatter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code></a> 实例,格式串可如下:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="s1">'ASCII section</span><span class="se">\ufeff</span><span class="s1">Unicode section'</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>用 UTF-8 编码时,Unicode 码位 U+FEFF 将会编码为 UTF-8 BOM——字节串 <code class="docutils literal notranslate"><span class="pre">b'\xef\xbb\xbf'</span></code> 。</p>
|
||
</li>
|
||
<li><p>用任意占位符替换 ASCII 部分,但要保证替换之后的数据一定是 ASCII 码(这样在 UTF-8 编码后就会维持不变)。</p></li>
|
||
<li><p>用任意占位符替换 Unicode 部分;如果替换后的数据包含超出 ASCII 范围的字符,没问题——他们将用 UTF-8 进行编码。</p></li>
|
||
</ol>
|
||
<p><code class="docutils literal notranslate"><span class="pre">SysLogHandler</span></code> <em>将</em> 对格式化后的日志信息进行 UTF-8 编码。如果遵循上述规则,应能生成符合 <span class="target" id="index-11"></span><a class="rfc reference external" href="https://tools.ietf.org/html/rfc5424.html"><strong>RFC 5424</strong></a> 的日志信息。否则,日志记录过程可能不会有什么反馈,但日志信息将不与 RFC 5424 兼容,syslog 守护程序可能会有出错反应。</p>
|
||
</section>
|
||
<section id="implementing-structured-logging">
|
||
<h2>结构化日志的实现代码<a class="headerlink" href="#implementing-structured-logging" title="永久链接至标题">¶</a></h2>
|
||
<p>大多数日志信息是供人阅读的,所以机器解析起来并不容易,但某些时候可能希望以结构化的格式输出,以 <em>能够</em> 被程序解析(无需用到复杂的正则表达式)。这可以直接用 logging 包实现。实现方式有很多,以下是一种比较简单的方案,利用 JSON 以机器可解析的方式对事件信息进行序列化:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">json</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="k">class</span> <span class="nc">StructuredMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">message</span> <span class="o">=</span> <span class="n">message</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="s1">'</span><span class="si">%s</span><span class="s1"> >>> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">message</span><span class="p">,</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">))</span>
|
||
|
||
<span class="n">_</span> <span class="o">=</span> <span class="n">StructuredMessage</span> <span class="c1"># optional, to improve readability</span>
|
||
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">_</span><span class="p">(</span><span class="s1">'message 1'</span><span class="p">,</span> <span class="n">foo</span><span class="o">=</span><span class="s1">'bar'</span><span class="p">,</span> <span class="n">bar</span><span class="o">=</span><span class="s1">'baz'</span><span class="p">,</span> <span class="n">num</span><span class="o">=</span><span class="mi">123</span><span class="p">,</span> <span class="n">fnum</span><span class="o">=</span><span class="mf">123.456</span><span class="p">))</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>上述代码运行后的结果是:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>message 1 >>> {"fnum": 123.456, "num": 123, "bar": "baz", "foo": "bar"}
|
||
</pre></div>
|
||
</div>
|
||
<p>请注意,根据 Python 版本的不同,各项数据的输出顺序可能会不一样。</p>
|
||
<p>若需进行更为定制化的处理,可以使用自定义 JSON 编码对象,下面给出完整示例:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">json</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="c1"># This next bit is to ensure the script runs unchanged on 2.x and 3.x</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">unicode</span>
|
||
<span class="k">except</span> <span class="ne">NameError</span><span class="p">:</span>
|
||
<span class="n">unicode</span> <span class="o">=</span> <span class="nb">str</span>
|
||
|
||
<span class="k">class</span> <span class="nc">Encoder</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">JSONEncoder</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="nf">default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">o</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="nb">set</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">o</span><span class="p">)</span>
|
||
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">o</span><span class="p">,</span> <span class="n">unicode</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">o</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">'unicode_escape'</span><span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'ascii'</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">default</span><span class="p">(</span><span class="n">o</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">StructuredMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">message</span> <span class="o">=</span> <span class="n">message</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="n">Encoder</span><span class="p">()</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="s1">'</span><span class="si">%s</span><span class="s1"> >>> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">message</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
|
||
|
||
<span class="n">_</span> <span class="o">=</span> <span class="n">StructuredMessage</span> <span class="c1"># optional, to improve readability</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">_</span><span class="p">(</span><span class="s1">'message 1'</span><span class="p">,</span> <span class="n">set_value</span><span class="o">=</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">},</span> <span class="n">snowman</span><span class="o">=</span><span class="s1">'</span><span class="se">\u2603</span><span class="s1">'</span><span class="p">))</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>上述代码运行后的结果是:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>message 1 >>> {"snowman": "\u2603", "set_value": [1, 2, 3]}
|
||
</pre></div>
|
||
</div>
|
||
<p>请注意,根据 Python 版本的不同,各项数据的输出顺序可能会不一样。</p>
|
||
</section>
|
||
<section id="customizing-handlers-with-dictconfig">
|
||
<span id="custom-handlers"></span><h2>利用 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 自定义 handler<a class="headerlink" href="#customizing-handlers-with-dictconfig" title="永久链接至标题">¶</a></h2>
|
||
<p>有时需要以特定方式自定义日志 handler,如果采用 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a>,可能无需生成子类就可以做到。比如要设置日志文件的所有权。在 POSIX 上,可以利用 <a class="reference internal" href="../library/shutil.html#shutil.chown" title="shutil.chown"><code class="xref py py-func docutils literal notranslate"><span class="pre">shutil.chown()</span></code></a> 轻松完成,但 stdlib 中的文件 handler 并不提供内置支持。于是可以用普通函数自定义 handler 的创建,例如:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">owned_file_handler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">'a'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">owner</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">owner</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
|
||
<span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">)</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
<span class="n">shutil</span><span class="o">.</span><span class="n">chown</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="o">*</span><span class="n">owner</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="n">encoding</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后,你可以在传给 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 的日志配置中指定通过调用此函数来创建日志处理程序:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'default'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(levelname)s</span><span class="s1"> </span><span class="si">%(name)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'file'</span><span class="p">:{</span>
|
||
<span class="c1"># The values below are popped from this dictionary and</span>
|
||
<span class="c1"># used to create the handler, set the handler's level and</span>
|
||
<span class="c1"># its formatter.</span>
|
||
<span class="s1">'()'</span><span class="p">:</span> <span class="n">owned_file_handler</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span><span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'default'</span><span class="p">,</span>
|
||
<span class="c1"># The values below are passed to the handler creator callable</span>
|
||
<span class="c1"># as keyword arguments.</span>
|
||
<span class="s1">'owner'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'pulse'</span><span class="p">,</span> <span class="s1">'pulse'</span><span class="p">],</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'chowntest.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'encoding'</span><span class="p">:</span> <span class="s1">'utf-8'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'file'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>出于演示目的,以下示例设置用户和用户组为 <code class="docutils literal notranslate"><span class="pre">pulse</span></code>。代码置于一个可运行的脚本文件 <code class="docutils literal notranslate"><span class="pre">chowntest.py</span></code> 中:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span><span class="o">,</span> <span class="nn">logging.config</span><span class="o">,</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">shutil</span>
|
||
|
||
<span class="k">def</span> <span class="nf">owned_file_handler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">'a'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">owner</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">owner</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
|
||
<span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">)</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
<span class="n">shutil</span><span class="o">.</span><span class="n">chown</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="o">*</span><span class="n">owner</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="n">encoding</span><span class="p">)</span>
|
||
|
||
<span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'default'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(levelname)s</span><span class="s1"> </span><span class="si">%(name)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'file'</span><span class="p">:{</span>
|
||
<span class="c1"># The values below are popped from this dictionary and</span>
|
||
<span class="c1"># used to create the handler, set the handler's level and</span>
|
||
<span class="c1"># its formatter.</span>
|
||
<span class="s1">'()'</span><span class="p">:</span> <span class="n">owned_file_handler</span><span class="p">,</span>
|
||
<span class="s1">'level'</span><span class="p">:</span><span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'default'</span><span class="p">,</span>
|
||
<span class="c1"># The values below are passed to the handler creator callable</span>
|
||
<span class="c1"># as keyword arguments.</span>
|
||
<span class="s1">'owner'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'pulse'</span><span class="p">,</span> <span class="s1">'pulse'</span><span class="p">],</span>
|
||
<span class="s1">'filename'</span><span class="p">:</span> <span class="s1">'chowntest.log'</span><span class="p">,</span>
|
||
<span class="s1">'mode'</span><span class="p">:</span> <span class="s1">'w'</span><span class="p">,</span>
|
||
<span class="s1">'encoding'</span><span class="p">:</span> <span class="s1">'utf-8'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'file'</span><span class="p">],</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">}</span>
|
||
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">LOGGING</span><span class="p">)</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'mylogger'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'A debug message'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>可能需要 <code class="docutils literal notranslate"><span class="pre">root</span></code> 权限才能运行:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>sudo<span class="w"> </span>python3.3<span class="w"> </span>chowntest.py
|
||
<span class="gp">$ </span>cat<span class="w"> </span>chowntest.log
|
||
<span class="go">2013-11-05 09:34:51,128 DEBUG mylogger A debug message</span>
|
||
<span class="gp">$ </span>ls<span class="w"> </span>-l<span class="w"> </span>chowntest.log
|
||
<span class="go">-rw-r--r-- 1 pulse pulse 55 2013-11-05 09:34 chowntest.log</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>请注意此示例用的是 Python 3.3,因为 <a class="reference internal" href="../library/shutil.html#shutil.chown" title="shutil.chown"><code class="xref py py-func docutils literal notranslate"><span class="pre">shutil.chown()</span></code></a> 是从此版本开始出现的。 此方式应当适用于任何支持 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 的 Python 版本 —— 例如 Python 2.7, 3.2 或更新的版本。 对于 3.3 之前的版本,你应当使用 <a class="reference internal" href="../library/os.html#os.chown" title="os.chown"><code class="xref py py-func docutils literal notranslate"><span class="pre">os.chown()</span></code></a> 之类的函数来实现实际的所有权修改。</p>
|
||
<p>实际应用中,handler 的创建函数可能位于项目的工具模块中。以下配置:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="s1">'()'</span><span class="p">:</span> <span class="n">owned_file_handler</span><span class="p">,</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>应使用:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="s1">'()'</span><span class="p">:</span> <span class="s1">'ext://project.util.owned_file_handler'</span><span class="p">,</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>这里的 <code class="docutils literal notranslate"><span class="pre">project.util</span></code> 可以换成函数所在包的实际名称。 在上述的可用脚本中,应该可以使用 <code class="docutils literal notranslate"><span class="pre">'ext://__main__.owned_file_handler'</span></code>。 在这里,实际的可调用对象是由 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 从 <code class="docutils literal notranslate"><span class="pre">ext://</span></code> 说明中解析出来的。</p>
|
||
<p>上述示例还指明了其他的文件修改类型的实现方案 —— 比如同样利用 <a class="reference internal" href="../library/os.html#os.chmod" title="os.chmod"><code class="xref py py-func docutils literal notranslate"><span class="pre">os.chmod()</span></code></a> 设置 POSIX 访问权限位。</p>
|
||
<p>当然,以上做法也可以扩展到 <a class="reference internal" href="../library/logging.handlers.html#logging.FileHandler" title="logging.FileHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">FileHandler</span></code></a> 之外的其他类型的 handler ——比如某个轮换文件 handler,或类型完全不同的其他 handler。</p>
|
||
</section>
|
||
<section id="using-particular-formatting-styles-throughout-your-application">
|
||
<span id="formatting-styles"></span><h2>生效于整个应用程序的格式化样式<a class="headerlink" href="#using-particular-formatting-styles-throughout-your-application" title="永久链接至标题">¶</a></h2>
|
||
<p>在 Python 3.2 中,<a class="reference internal" href="../library/logging.html#logging.Formatter" title="logging.Formatter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code></a> 增加了一个 <code class="docutils literal notranslate"><span class="pre">style</span></code> 关键字形参,它默认为 <code class="docutils literal notranslate"><span class="pre">%</span></code> 以便向下兼容,但是允许采用 <code class="docutils literal notranslate"><span class="pre">{</span></code> 或 <code class="docutils literal notranslate"><span class="pre">$</span></code> 来支持 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> 和 <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a> 所支持的格式化方式。 请注意此形参控制着用用于最终输出到日志的日志消息格式,并且与单独日志消息的构造方式完全无关。</p>
|
||
<p>日志函数(<a class="reference internal" href="../library/logging.html#logging.Logger.debug" title="logging.Logger.debug"><code class="xref py py-meth docutils literal notranslate"><span class="pre">debug()</span></code></a>, <a class="reference internal" href="../library/logging.html#logging.Logger.info" title="logging.Logger.info"><code class="xref py py-meth docutils literal notranslate"><span class="pre">info()</span></code></a> 等)只会读取位置参数获取日志信息本身,而关键字参数仅用于确定日志函数的工作选项(比如关键字参数 <code class="docutils literal notranslate"><span class="pre">exc_info</span></code> 表示应将跟踪信息记入日志,关键字参数 <code class="docutils literal notranslate"><span class="pre">extra</span></code> 则给出了需加入日志的额外上下文信息)。所以不能直接使用 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> 或 <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a> 这种语法进行日志调用,因为日志包在内部使用 %-f 格式来合并格式串和可变参数。因为尚需保持向下兼容,这一点不会改变,已有代码中的所有日志调用都将采用 %-f 格式串。</p>
|
||
<p>有人建议将格式化样式与特定的日志对象进行关联,但其实也会遇到向下兼容的问题,因为已有代码可能用到了某日志对象并采用了 %-f 格式串。</p>
|
||
<p>为了让第三方库和自编代码都能够交互使用日志功能,需要决定在单次日志记录调用级别采用什么格式。于是就出现了其他几种格式化样式方案。</p>
|
||
<section id="using-logrecord-factories">
|
||
<h3>LogRecord 工厂的用法<a class="headerlink" href="#using-logrecord-factories" title="永久链接至标题">¶</a></h3>
|
||
<p>在 Python 3.2 中,伴随着 <a class="reference internal" href="../library/logging.html#logging.Formatter" title="logging.Formatter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code></a> 的上述变化,logging 包增加了允许用户使用 <a class="reference internal" href="../library/logging.html#logging.setLogRecordFactory" title="logging.setLogRecordFactory"><code class="xref py py-func docutils literal notranslate"><span class="pre">setLogRecordFactory()</span></code></a> 函数来。设置自己的 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 子类的功能。 你可以使用此功能来设置自己的 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 子类,它会通过重写 <a class="reference internal" href="../library/logging.html#logging.LogRecord.getMessage" title="logging.LogRecord.getMessage"><code class="xref py py-meth docutils literal notranslate"><span class="pre">getMessage()</span></code></a> 方法来完成适当的操作。 <code class="docutils literal notranslate"><span class="pre">msg</span> <span class="pre">%</span> <span class="pre">args</span></code> 格式化是在此方法的基类实现中进行的,你可以在那里用你自己的格式化操作来替换;但是,你应当注意要支持全部的格式化样式并允许将 %-formatting 作为默认样式,以确保与其他代码进行配合。 还应当注意调用 <code class="docutils literal notranslate"><span class="pre">str(self.msg)</span></code>,正如基类实现所做的一样。</p>
|
||
<p>更多信息请参阅 <a class="reference internal" href="../library/logging.html#logging.setLogRecordFactory" title="logging.setLogRecordFactory"><code class="xref py py-func docutils literal notranslate"><span class="pre">setLogRecordFactory()</span></code></a> 和 <a class="reference internal" href="../library/logging.html#logging.LogRecord" title="logging.LogRecord"><code class="xref py py-class docutils literal notranslate"><span class="pre">LogRecord</span></code></a> 的参考文档。</p>
|
||
</section>
|
||
<section id="using-custom-message-objects">
|
||
<h3>自定义信息对象的使用<a class="headerlink" href="#using-custom-message-objects" title="永久链接至标题">¶</a></h3>
|
||
<p>另一种方案可能更为简单,可以利用 {}- 和 $- 格式构建自己的日志消息。大家或许还记得(来自 <a class="reference internal" href="logging.html#arbitrary-object-messages"><span class="std std-ref">使用任意对象作为消息</span></a>),可以用任意对象作为日志信息的格式串,日志包将调用该对象上 <a class="reference internal" href="../library/stdtypes.html#str" title="str"><code class="xref py py-func docutils literal notranslate"><span class="pre">str()</span></code></a> 获取实际的格式串。看下以下两个类:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">BraceMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fmt</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">fmt</span> <span class="o">=</span> <span class="n">fmt</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">args</span> <span class="o">=</span> <span class="n">args</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">fmt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span>
|
||
|
||
<span class="k">class</span> <span class="nc">DollarMessage</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fmt</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">fmt</span> <span class="o">=</span> <span class="n">fmt</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span> <span class="o">=</span> <span class="n">kwargs</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="kn">from</span> <span class="nn">string</span> <span class="kn">import</span> <span class="n">Template</span>
|
||
<span class="k">return</span> <span class="n">Template</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">fmt</span><span class="p">)</span><span class="o">.</span><span class="n">substitute</span><span class="p">(</span><span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>以上两个类均都可用于替代格式串,以便用 {}- 或 $-formatting 构建实际的“日志信息”部分,此部分将出现在格式化后的日志输出中,替换 %(message)s 、“{message}”或“$message”。每次要写入日志时都使用类名,如果觉得使用不便,可以采用 <code class="docutils literal notranslate"><span class="pre">M</span></code> 或 <code class="docutils literal notranslate"><span class="pre">_</span></code> 之类的别名(如果将 <code class="docutils literal notranslate"><span class="pre">_</span></code> 用于本地化操作,则可用 <code class="docutils literal notranslate"><span class="pre">__</span></code>)。</p>
|
||
<p>下面给出示例。 首先用 <a class="reference internal" href="../library/stdtypes.html#str.format" title="str.format"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.format()</span></code></a> 进行格式化:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">__</span> <span class="o">=</span> <span class="n">BraceMessage</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with </span><span class="si">{0}</span><span class="s1"> </span><span class="si">{1}</span><span class="s1">'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'placeholders'</span><span class="p">))</span>
|
||
<span class="go">Message with 2 placeholders</span>
|
||
<span class="gp">>>> </span><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">p</span> <span class="o">=</span> <span class="n">Point</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">p</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
<span class="gp">>>> </span><span class="n">p</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with coordinates: (</span><span class="si">{point.x:.2f}</span><span class="s1">, </span><span class="si">{point.y:.2f}</span><span class="s1">)'</span><span class="p">,</span> <span class="n">point</span><span class="o">=</span><span class="n">p</span><span class="p">))</span>
|
||
<span class="go">Message with coordinates: (0.50, 0.50)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后,用 <a class="reference internal" href="../library/string.html#string.Template" title="string.Template"><code class="xref py py-class docutils literal notranslate"><span class="pre">string.Template</span></code></a> 格式化:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">__</span> <span class="o">=</span> <span class="n">DollarMessage</span>
|
||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">__</span><span class="p">(</span><span class="s1">'Message with $num $what'</span><span class="p">,</span> <span class="n">num</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">what</span><span class="o">=</span><span class="s1">'placeholders'</span><span class="p">))</span>
|
||
<span class="go">Message with 2 placeholders</span>
|
||
<span class="gp">>>></span>
|
||
</pre></div>
|
||
</div>
|
||
<p>值得注意的是,上述做法对性能并没什么影响:格式化过程其实不是在日志调用时发生的,而是在日志信息即将由 handler 输出到日志时发生。因此,唯一可能让人困惑的稍不寻常的地方,就是包裹在格式串和参数外面的括号,而不是格式串。因为 __ 符号只是对 <code class="docutils literal notranslate"><span class="pre">XXXMessage</span></code> 类的构造函数调用的语法糖。</p>
|
||
</section>
|
||
</section>
|
||
<section id="configuring-filters-with-dictconfig">
|
||
<span id="filters-dictconfig"></span><h2>利用 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> 定义过滤器<a class="headerlink" href="#configuring-filters-with-dictconfig" title="永久链接至标题">¶</a></h2>
|
||
<p>用 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> <em>可以</em> 对日志过滤器进行设置,尽管乍一看做法并不明显(所以才需要本秘籍)。 由于 <a class="reference internal" href="../library/logging.html#logging.Filter" title="logging.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code></a> 是标准库中唯一的日志过滤器类,不太可能满足众多的要求(它只是作为基类存在),通常需要定义自己的 <a class="reference internal" href="../library/logging.html#logging.Filter" title="logging.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code></a> 子类,并重写 <a class="reference internal" href="../library/logging.html#logging.Filter.filter" title="logging.Filter.filter"><code class="xref py py-meth docutils literal notranslate"><span class="pre">filter()</span></code></a> 方法。为此,请在过滤器的配置字典中设置 <code class="docutils literal notranslate"><span class="pre">()</span></code> 键,指定要用于创建过滤器的可调用对象(最明显可用的就是给出一个类,但也可以提供任何一个可调用对象,只要能返回 <a class="reference internal" href="../library/logging.html#logging.Filter" title="logging.Filter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Filter</span></code></a> 实例即可)。下面是一个完整的例子:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.config</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
<span class="k">class</span> <span class="nc">MyFilter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Filter</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">param</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">param</span> <span class="o">=</span> <span class="n">param</span>
|
||
|
||
<span class="k">def</span> <span class="nf">filter</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">param</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">allow</span> <span class="o">=</span> <span class="kc">True</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">allow</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">param</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">record</span><span class="o">.</span><span class="n">msg</span>
|
||
<span class="k">if</span> <span class="n">allow</span><span class="p">:</span>
|
||
<span class="n">record</span><span class="o">.</span><span class="n">msg</span> <span class="o">=</span> <span class="s1">'changed: '</span> <span class="o">+</span> <span class="n">record</span><span class="o">.</span><span class="n">msg</span>
|
||
<span class="k">return</span> <span class="n">allow</span>
|
||
|
||
<span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'filters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'myfilter'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'()'</span><span class="p">:</span> <span class="n">MyFilter</span><span class="p">,</span>
|
||
<span class="s1">'param'</span><span class="p">:</span> <span class="s1">'noshow'</span><span class="p">,</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'console'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'filters'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'myfilter'</span><span class="p">]</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'level'</span><span class="p">:</span> <span class="s1">'DEBUG'</span><span class="p">,</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console'</span><span class="p">]</span>
|
||
<span class="p">},</span>
|
||
<span class="p">}</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">LOGGING</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'hello - noshow'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>以上示例展示了将配置数据传给构造实例的可调用对象,形式是关键字参数。运行后将会输出:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>changed: hello
|
||
</pre></div>
|
||
</div>
|
||
<p>这说明过滤器按照配置的参数生效了。</p>
|
||
<p>需要额外注意的地方:</p>
|
||
<ul class="simple">
|
||
<li><p>如果在配置中无法直接引用可调用对象(比如位于不同的模块中,并且不能在配置字典所在的位置直接导入),则可以采用 <code class="docutils literal notranslate"><span class="pre">ext://...</span></code> 的形式,正如 <a class="reference internal" href="../library/logging.config.html#logging-config-dict-externalobj"><span class="std std-ref">访问外部对象</span></a> 所述。例如,在上述示例中可以使用文本 <code class="docutils literal notranslate"><span class="pre">'ext://__main__.MyFilter'</span></code> 而不是 <code class="docutils literal notranslate"><span class="pre">MyFilter</span></code> 对象。</p></li>
|
||
<li><p>与过滤器一样,上述技术还可用于配置自定义 handler 和格式化对象。有关如何在日志配置中使用用户自定义对象的更多信息,请参阅 <a class="reference internal" href="../library/logging.config.html#logging-config-dict-userdef"><span class="std std-ref">用户定义对象</span></a>,以及上述 <a class="reference internal" href="#custom-handlers"><span class="std std-ref">利用 dictConfig() 自定义 handler</span></a> 的其他指南。</p></li>
|
||
</ul>
|
||
</section>
|
||
<section id="customized-exception-formatting">
|
||
<span id="custom-format-exception"></span><h2>异常信息的自定义格式化<a class="headerlink" href="#customized-exception-formatting" title="永久链接至标题">¶</a></h2>
|
||
<p>有时可能需要设置自定义的异常信息格式——考虑到会用到参数,假定要让每条日志事件只占一行,即便存在异常信息也一样。这可以用自定义格式化类来实现,如下所示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="k">class</span> <span class="nc">OneLineExceptionFormatter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="nf">formatException</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_info</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Format an exception so that it prints on a single line.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">result</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">formatException</span><span class="p">(</span><span class="n">exc_info</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="nb">repr</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="c1"># or format into one line however you want to</span>
|
||
|
||
<span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">record</span><span class="o">.</span><span class="n">exc_text</span><span class="p">:</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span> <span class="o">+</span> <span class="s1">'|'</span>
|
||
<span class="k">return</span> <span class="n">s</span>
|
||
|
||
<span class="k">def</span> <span class="nf">configure_logging</span><span class="p">():</span>
|
||
<span class="n">fh</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="s1">'output.txt'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="n">OneLineExceptionFormatter</span><span class="p">(</span><span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1">|</span><span class="si">%(levelname)s</span><span class="s1">|</span><span class="si">%(message)s</span><span class="s1">|'</span><span class="p">,</span>
|
||
<span class="s1">'</span><span class="si">%d</span><span class="s1">/%m/%Y %H:%M:%S'</span><span class="p">)</span>
|
||
<span class="n">fh</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">fh</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">configure_logging</span><span class="p">()</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Sample message'</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">x</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="mi">0</span>
|
||
<span class="k">except</span> <span class="ne">ZeroDivisionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">exception</span><span class="p">(</span><span class="s1">'ZeroDivisionError: </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>运行后将会生成只有两行信息的文件:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>28/01/2015 07:21:23|INFO|Sample message|
|
||
28/01/2015 07:21:23|ERROR|ZeroDivisionError: integer division or modulo by zero|'Traceback (most recent call last):\n File "logtest7.py", line 30, in main\n x = 1 / 0\nZeroDivisionError: integer division or modulo by zero'|
|
||
</pre></div>
|
||
</div>
|
||
<p>虽然上述处理方式很简单,但也给出了根据喜好对异常信息进行格式化输出的方案。或许 <a class="reference internal" href="../library/traceback.html#module-traceback" title="traceback: Print or retrieve a stack traceback."><code class="xref py py-mod docutils literal notranslate"><span class="pre">traceback</span></code></a> 模块能满足更专门的需求。</p>
|
||
</section>
|
||
<section id="speaking-logging-messages">
|
||
<span id="spoken-messages"></span><h2>语音播报日志信息<a class="headerlink" href="#speaking-logging-messages" title="永久链接至标题">¶</a></h2>
|
||
<p>有时可能需要以声音的形式呈现日志消息。如果系统自带了文本转语音 (TTS)功能,即便没与 Python 关联也很容易做到。大多数 TTS 系统都有一个可运行的命令行程序,在 handler 中可以用 <a class="reference internal" href="../library/subprocess.html#module-subprocess" title="subprocess: Subprocess management."><code class="xref py py-mod docutils literal notranslate"><span class="pre">subprocess</span></code></a> 进行调用。这里假定 TTS 命令行程序不会与用户交互,或需要很长时间才会执行完毕,写入日志的信息也不会多到影响用户查看,并且可以接受每次播报一条信息,以下示例实现了等一条信息播完再处理下一条,可能会导致其他 handler 的等待。这个简短示例仅供演示,假定 <code class="docutils literal notranslate"><span class="pre">espeak</span></code> TTS 包已就绪:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">subprocess</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
<span class="k">class</span> <span class="nc">TTSHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Handler</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="nf">emit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="n">msg</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
<span class="c1"># Speak slowly in a female English voice</span>
|
||
<span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'espeak'</span><span class="p">,</span> <span class="s1">'-s150'</span><span class="p">,</span> <span class="s1">'-ven+f3'</span><span class="p">,</span> <span class="n">msg</span><span class="p">]</span>
|
||
<span class="n">p</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
|
||
<span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">STDOUT</span><span class="p">)</span>
|
||
<span class="c1"># wait for the program to finish</span>
|
||
<span class="n">p</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">configure_logging</span><span class="p">():</span>
|
||
<span class="n">h</span> <span class="o">=</span> <span class="n">TTSHandler</span><span class="p">()</span>
|
||
<span class="n">root</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
|
||
<span class="c1"># the default formatter just returns the message</span>
|
||
<span class="n">root</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Hello'</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Goodbye'</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">configure_logging</span><span class="p">()</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>运行后将会以女声播报“Hello”和“Goodbye”。</p>
|
||
<p>当然,上述方案也适用于其他 TTS 系统,甚至可以通过利用命令行运行的外部程序来处理消息。</p>
|
||
</section>
|
||
<section id="buffering-logging-messages-and-outputting-them-conditionally">
|
||
<span id="buffered-logging"></span><h2>缓冲日志消息并有条件地输出它们<a class="headerlink" href="#buffering-logging-messages-and-outputting-them-conditionally" title="永久链接至标题">¶</a></h2>
|
||
<p>在某些情况下,你可能希望在临时区域中记录日志消息,并且只在发生某种特定的情况下才输出它们。 例如,你可能希望起始在函数中记录调试事件,如果函数执行完成且没有错误,你不希望输出收集的调试信息以避免造成日志混乱,但如果出现错误,那么你希望所有调试以及错误消息被输出。</p>
|
||
<p>下面是一个示例,展示如何在你的日志记录函数上使用装饰器以实现这一功能。该示例使用 <a class="reference internal" href="../library/logging.handlers.html#logging.handlers.MemoryHandler" title="logging.handlers.MemoryHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">logging.handlers.MemoryHandler</span></code></a> ,它允许缓冲已记录的事件直到某些条件发生,缓冲的事件才会被刷新(<code class="docutils literal notranslate"><span class="pre">flushed</span></code>) - 传递给另一个处理程序( <code class="docutils literal notranslate"><span class="pre">target</span></code> handler)进行处理。 默认情况下, <code class="docutils literal notranslate"><span class="pre">MemoryHandler</span></code> 在其缓冲区被填满时被刷新,或者看到一个级别大于或等于指定阈值的事件。 如果想要自定义刷新行为,你可以通过更专业的 <code class="docutils literal notranslate"><span class="pre">MemoryHandler</span></code> 子类来使用这个秘诀。</p>
|
||
<p>这个示例脚本有一个简单的函数 <code class="docutils literal notranslate"><span class="pre">foo</span></code> ,它只是在所有的日志级别中循环运行,写到 <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code> ,说明它要记录在哪个级别上,然后在这个级别上实际记录一个消息。你可以给 <code class="docutils literal notranslate"><span class="pre">foo</span></code> 传递一个参数,如果为 true ,它将在ERROR和CRITICAL级别记录,否则,它只在DEBUG、INFO和WARNING级别记录。</p>
|
||
<p>脚本只是使用了一个装饰器来装饰 <code class="docutils literal notranslate"><span class="pre">foo</span></code>,这个装饰器将记录执行所需的条件。装饰器使用一个记录器作为参数,并在调用被装饰的函数期间附加一个内存处理程序。装饰器可以使用目标处理程序、记录级别和缓冲区的容量(缓冲记录的数量)来附加参数。这些参数分别默认为写入 <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code> 的 <a class="reference internal" href="../library/logging.handlers.html#logging.StreamHandler" title="logging.StreamHandler"><code class="xref py py-class docutils literal notranslate"><span class="pre">StreamHandler</span></code></a> , <code class="docutils literal notranslate"><span class="pre">logging.ERROR</span></code> 和 <code class="docutils literal notranslate"><span class="pre">100</span></code>。</p>
|
||
<p>以下是脚本:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">from</span> <span class="nn">logging.handlers</span> <span class="kn">import</span> <span class="n">MemoryHandler</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">NullHandler</span><span class="p">())</span>
|
||
|
||
<span class="k">def</span> <span class="nf">log_if_errors</span><span class="p">(</span><span class="n">logger</span><span class="p">,</span> <span class="n">target_handler</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">flush_level</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">capacity</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">target_handler</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">target_handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="n">flush_level</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">flush_level</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span>
|
||
<span class="k">if</span> <span class="n">capacity</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="n">capacity</span> <span class="o">=</span> <span class="mi">100</span>
|
||
<span class="n">handler</span> <span class="o">=</span> <span class="n">MemoryHandler</span><span class="p">(</span><span class="n">capacity</span><span class="p">,</span> <span class="n">flushLevel</span><span class="o">=</span><span class="n">flush_level</span><span class="p">,</span> <span class="n">target</span><span class="o">=</span><span class="n">target_handler</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">fn</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">fn</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">exception</span><span class="p">(</span><span class="s1">'call failed'</span><span class="p">)</span>
|
||
<span class="k">raise</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="nb">super</span><span class="p">(</span><span class="n">MemoryHandler</span><span class="p">,</span> <span class="n">handler</span><span class="p">)</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">removeHandler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">wrapper</span>
|
||
|
||
<span class="k">return</span> <span class="n">decorator</span>
|
||
|
||
<span class="k">def</span> <span class="nf">write_line</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s1">'</span><span class="si">%s</span><span class="se">\n</span><span class="s1">'</span> <span class="o">%</span> <span class="n">s</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">fail</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'about to log at DEBUG ...'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Actually logged at DEBUG'</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'about to log at INFO ...'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Actually logged at INFO'</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'about to log at WARNING ...'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'Actually logged at WARNING'</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">fail</span><span class="p">:</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'about to log at ERROR ...'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'Actually logged at ERROR'</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'about to log at CRITICAL ...'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'Actually logged at CRITICAL'</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">fail</span>
|
||
|
||
<span class="n">decorated_foo</span> <span class="o">=</span> <span class="n">log_if_errors</span><span class="p">(</span><span class="n">logger</span><span class="p">)(</span><span class="n">foo</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'Calling undecorated foo with False'</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">foo</span><span class="p">(</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'Calling undecorated foo with True'</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">foo</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'Calling decorated foo with False'</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">decorated_foo</span><span class="p">(</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="n">write_line</span><span class="p">(</span><span class="s1">'Calling decorated foo with True'</span><span class="p">)</span>
|
||
<span class="k">assert</span> <span class="n">decorated_foo</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>运行此脚本时,应看到以下输出:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>Calling undecorated foo with False
|
||
about to log at DEBUG ...
|
||
about to log at INFO ...
|
||
about to log at WARNING ...
|
||
Calling undecorated foo with True
|
||
about to log at DEBUG ...
|
||
about to log at INFO ...
|
||
about to log at WARNING ...
|
||
about to log at ERROR ...
|
||
about to log at CRITICAL ...
|
||
Calling decorated foo with False
|
||
about to log at DEBUG ...
|
||
about to log at INFO ...
|
||
about to log at WARNING ...
|
||
Calling decorated foo with True
|
||
about to log at DEBUG ...
|
||
about to log at INFO ...
|
||
about to log at WARNING ...
|
||
about to log at ERROR ...
|
||
Actually logged at DEBUG
|
||
Actually logged at INFO
|
||
Actually logged at WARNING
|
||
Actually logged at ERROR
|
||
about to log at CRITICAL ...
|
||
Actually logged at CRITICAL
|
||
</pre></div>
|
||
</div>
|
||
<p>如你所见,实际日志记录输出仅在消息等级为ERROR或更高的事件时发生,但在这种情况下,任何之前较低消息等级的事件还会被记录。</p>
|
||
<p>你当然可以使用传统的装饰方法:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="nd">@log_if_errors</span><span class="p">(</span><span class="n">logger</span><span class="p">)</span>
|
||
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">fail</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="formatting-times-using-utc-gmt-via-configuration">
|
||
<span id="utc-formatting"></span><h2>通过配置使用UTC (GMT) 格式化时间<a class="headerlink" href="#formatting-times-using-utc-gmt-via-configuration" title="永久链接至标题">¶</a></h2>
|
||
<p>有时需要将时间格式化为 UTC 格式,可以用类似 <cite>UTCFormatter</cite> 的类来完成,如下所示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="k">class</span> <span class="nc">UTCFormatter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">):</span>
|
||
<span class="n">converter</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">gmtime</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后你可以在你的代码中使用 <code class="docutils literal notranslate"><span class="pre">UTCFormatter</span></code>,而不是 <a class="reference internal" href="../library/logging.html#logging.Formatter" title="logging.Formatter"><code class="xref py py-class docutils literal notranslate"><span class="pre">Formatter</span></code></a>。 如果你想通过配置来实现这一功能,你可以使用 <a class="reference internal" href="../library/logging.config.html#logging.config.dictConfig" title="logging.config.dictConfig"><code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code></a> API 来完成,该方法在以下完整示例中展示:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">logging.config</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="k">class</span> <span class="nc">UTCFormatter</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">):</span>
|
||
<span class="n">converter</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">gmtime</span>
|
||
|
||
<span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s1">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||
<span class="s1">'disable_existing_loggers'</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
|
||
<span class="s1">'formatters'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'utc'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'()'</span><span class="p">:</span> <span class="n">UTCFormatter</span><span class="p">,</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'local'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'console1'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'utc'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'console2'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'class'</span><span class="p">:</span> <span class="s1">'logging.StreamHandler'</span><span class="p">,</span>
|
||
<span class="s1">'formatter'</span><span class="p">:</span> <span class="s1">'local'</span><span class="p">,</span>
|
||
<span class="p">},</span>
|
||
<span class="p">},</span>
|
||
<span class="s1">'root'</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s1">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'console1'</span><span class="p">,</span> <span class="s1">'console2'</span><span class="p">],</span>
|
||
<span class="p">}</span>
|
||
<span class="p">}</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dictConfig</span><span class="p">(</span><span class="n">LOGGING</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">'The local time is </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">asctime</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>脚本会运行输出类似下面的内容:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>2015-10-17 12:53:29,501 The local time is Sat Oct 17 13:53:29 2015
|
||
2015-10-17 13:53:29,501 The local time is Sat Oct 17 13:53:29 2015
|
||
</pre></div>
|
||
</div>
|
||
<p>展示了如何将时间格式化为本地时间和UTC两种形式,其中每种形式对应一个日志处理器 。</p>
|
||
</section>
|
||
<section id="using-a-context-manager-for-selective-logging">
|
||
<span id="context-manager"></span><h2>使用上下文管理器的可选的日志记录<a class="headerlink" href="#using-a-context-manager-for-selective-logging" title="永久链接至标题">¶</a></h2>
|
||
<p>有时候,我们需要暂时更改日志配置,并在执行某些操作后将其还原。为此,上下文管理器是实现保存和恢复日志上下文的最明显的方式。这是一个关于上下文管理器的简单例子,它允许你在上下文管理器的作用域内更改日志记录等级以及增加日志处理器:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
<span class="k">class</span> <span class="nc">LoggingContext</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">logger</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">handler</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">close</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="n">logger</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">level</span> <span class="o">=</span> <span class="n">level</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handler</span> <span class="o">=</span> <span class="n">handler</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">close</span> <span class="o">=</span> <span class="n">close</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">level</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">old_level</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">level</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">level</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">handler</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">handler</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">et</span><span class="p">,</span> <span class="n">ev</span><span class="p">,</span> <span class="n">tb</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">level</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">old_level</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">handler</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">removeHandler</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">handler</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">handler</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">close</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handler</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
<span class="c1"># implicit return of None => don't swallow exceptions</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>如果指定上下文管理器的日志记录等级属性,则在上下文管理器的with语句所涵盖的代码中,日志记录器的记录等级将临时设置为上下文管理器所配置的日志记录等级。 如果指定上下文管理的日志处理器属性,则该句柄在进入上下文管理器的上下文时添加到记录器中,并在退出时被删除。 如果你再也不需要该日志处理器时,你可以让上下文管理器在退出上下文管理器的上下文时关闭它。</p>
|
||
<p>为了说明它是如何工作的,我们可以在上面添加以下代码块:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s1">'foo'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">())</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'1. This should appear just once on stderr.'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'2. This should not appear.'</span><span class="p">)</span>
|
||
<span class="k">with</span> <span class="n">LoggingContext</span><span class="p">(</span><span class="n">logger</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">):</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'3. This should appear once on stderr.'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'4. This should not appear.'</span><span class="p">)</span>
|
||
<span class="n">h</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="p">)</span>
|
||
<span class="k">with</span> <span class="n">LoggingContext</span><span class="p">(</span><span class="n">logger</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">handler</span><span class="o">=</span><span class="n">h</span><span class="p">,</span> <span class="n">close</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'5. This should appear twice - once on stderr and once on stdout.'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'6. This should appear just once on stderr.'</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'7. This should not appear.'</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>我们最初设置日志记录器的消息等级为 <code class="docutils literal notranslate"><span class="pre">INFO</span></code> ,因此消息#1出现,消息#2没有出现。在接下来的 <code class="docutils literal notranslate"><span class="pre">with</span></code> 代码块中我们暂时将消息等级变更为 <code class="docutils literal notranslate"><span class="pre">DEBUG</span></code> ,从而消息 #3 出现。在这一代码块退出后,日志记录器的消息等级恢复为 <code class="docutils literal notranslate"><span class="pre">INFO</span></code> ,从而消息 #4 没有出现。在下一个 <code class="docutils literal notranslate"><span class="pre">with</span></code> 代码块中,我们再一次将设置消息等级设置为 <code class="docutils literal notranslate"><span class="pre">DEBUG</span></code> ,同时添加一个将消息写入 <code class="docutils literal notranslate"><span class="pre">sys.stdout</span></code> 的日志处理器。因此,消息#5在控制台出现两次 (分别通过 <code class="docutils literal notranslate"><span class="pre">stderr</span></code> 和 <code class="docutils literal notranslate"><span class="pre">stdout</span></code> )。在 <code class="docutils literal notranslate"><span class="pre">with</span></code> 语句完成后,状态与之前一样,因此消息 #6 出现(类似消息 #1),而消息 #7 没有出现(类似消息 #2)。</p>
|
||
<p>如果我们运行生成的脚本,结果如下:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>logctx.py
|
||
<span class="go">1. This should appear just once on stderr.</span>
|
||
<span class="go">3. This should appear once on stderr.</span>
|
||
<span class="go">5. This should appear twice - once on stderr and once on stdout.</span>
|
||
<span class="go">5. This should appear twice - once on stderr and once on stdout.</span>
|
||
<span class="go">6. This should appear just once on stderr.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>我们将 <code class="docutils literal notranslate"><span class="pre">stderr</span></code> 标准错误重定向到 <code class="docutils literal notranslate"><span class="pre">/dev/null</span></code> ,我再次运行生成的脚步,唯一被写入 <code class="docutils literal notranslate"><span class="pre">stdout</span></code> 标准输出的消息,即我们所能看见的消息,如下:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>logctx.py<span class="w"> </span><span class="m">2</span>>/dev/null
|
||
<span class="go">5. This should appear twice - once on stderr and once on stdout.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>再一次,将 <code class="docutils literal notranslate"><span class="pre">stdout</span></code> 标准输出重定向到 <code class="docutils literal notranslate"><span class="pre">/dev/null</span></code>,我获得如下结果:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>logctx.py<span class="w"> </span>>/dev/null
|
||
<span class="go">1. This should appear just once on stderr.</span>
|
||
<span class="go">3. This should appear once on stderr.</span>
|
||
<span class="go">5. This should appear twice - once on stderr and once on stdout.</span>
|
||
<span class="go">6. This should appear just once on stderr.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>在这种情况下,与预期一致,打印到 <code class="docutils literal notranslate"><span class="pre">stdout</span></code> 标准输出的消息#5不会出现。</p>
|
||
<p>当然,这里描述的方法可以被推广,例如临时附加日志记录过滤器。 请注意,上面的代码适用于Python 2以及Python 3。</p>
|
||
</section>
|
||
<section id="a-cli-application-starter-template">
|
||
<span id="starter-template"></span><h2>命令行日志应用起步<a class="headerlink" href="#a-cli-application-starter-template" title="永久链接至标题">¶</a></h2>
|
||
<p>下面的示例提供了如下功能:</p>
|
||
<ul class="simple">
|
||
<li><p>根据命令行参数确定日志级别</p></li>
|
||
<li><p>在单独的文件中分发多条子命令,同一级别的子命令均以一致的方式记录。</p></li>
|
||
<li><p>最简单的配置用法</p></li>
|
||
</ul>
|
||
<p>假定有一个命令行应用程序,用于停止、启动或重新启动某些服务。为了便于演示,不妨将 <code class="docutils literal notranslate"><span class="pre">app.py</span></code> 作为应用程序的主代码文件,并在 <code class="docutils literal notranslate"><span class="pre">start.py</span></code> 、 <code class="docutils literal notranslate"><span class="pre">stop.py</span></code> 和 <code class="docutils literal notranslate"><span class="pre">restart.py</span></code> 中实现单独的命令。再假定要通过命令行参数控制应用程序的日志粒度,默认为 <code class="docutils literal notranslate"><span class="pre">logging.INFO</span></code> 。以下是 <code class="docutils literal notranslate"><span class="pre">app.py</span></code> 的一个示例:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">argparse</span>
|
||
<span class="kn">import</span> <span class="nn">importlib</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">os</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="n">scriptname</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
|
||
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">scriptname</span><span class="p">)</span>
|
||
<span class="n">levels</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'DEBUG'</span><span class="p">,</span> <span class="s1">'INFO'</span><span class="p">,</span> <span class="s1">'WARNING'</span><span class="p">,</span> <span class="s1">'ERROR'</span><span class="p">,</span> <span class="s1">'CRITICAL'</span><span class="p">)</span>
|
||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'--log-level'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">'INFO'</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="n">levels</span><span class="p">)</span>
|
||
<span class="n">subparsers</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_subparsers</span><span class="p">(</span><span class="n">dest</span><span class="o">=</span><span class="s1">'command'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Available commands:'</span><span class="p">)</span>
|
||
<span class="n">start_cmd</span> <span class="o">=</span> <span class="n">subparsers</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">'start'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'Start a service'</span><span class="p">)</span>
|
||
<span class="n">start_cmd</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="n">metavar</span><span class="o">=</span><span class="s1">'NAME'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Name of service to start'</span><span class="p">)</span>
|
||
<span class="n">stop_cmd</span> <span class="o">=</span> <span class="n">subparsers</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">'stop'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Stop one or more services'</span><span class="p">)</span>
|
||
<span class="n">stop_cmd</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'names'</span><span class="p">,</span> <span class="n">metavar</span><span class="o">=</span><span class="s1">'NAME'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">'+'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Name of service to stop'</span><span class="p">)</span>
|
||
<span class="n">restart_cmd</span> <span class="o">=</span> <span class="n">subparsers</span><span class="o">.</span><span class="n">add_parser</span><span class="p">(</span><span class="s1">'restart'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Restart one or more services'</span><span class="p">)</span>
|
||
<span class="n">restart_cmd</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'names'</span><span class="p">,</span> <span class="n">metavar</span><span class="o">=</span><span class="s1">'NAME'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">'+'</span><span class="p">,</span>
|
||
<span class="n">help</span><span class="o">=</span><span class="s1">'Name of service to restart'</span><span class="p">)</span>
|
||
<span class="n">options</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
|
||
<span class="c1"># the code to dispatch commands could all be in this file. For the purposes</span>
|
||
<span class="c1"># of illustration only, we implement each command in a separate module.</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">mod</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">import_module</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">command</span><span class="p">)</span>
|
||
<span class="n">cmd</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="s1">'command'</span><span class="p">)</span>
|
||
<span class="k">except</span> <span class="p">(</span><span class="ne">ImportError</span><span class="p">,</span> <span class="ne">AttributeError</span><span class="p">):</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'Unable to find the code for command </span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1">'</span> <span class="o">%</span> <span class="n">options</span><span class="o">.</span><span class="n">command</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="mi">1</span>
|
||
<span class="c1"># Could get fancy here and load configuration from file or dictionary</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">options</span><span class="o">.</span><span class="n">log_level</span><span class="p">,</span>
|
||
<span class="nb">format</span><span class="o">=</span><span class="s1">'</span><span class="si">%(levelname)s</span><span class="s1"> </span><span class="si">%(name)s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">cmd</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">start</span></code>、<code class="docutils literal notranslate"><span class="pre">stop</span></code> 和 <code class="docutils literal notranslate"><span class="pre">restart</span></code> 命令可以在单独的模块中实现,启动命令的代码可如下:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># start.py</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">command</span><span class="p">(</span><span class="n">options</span><span class="p">):</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'About to start </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="c1"># actually do the command processing here ...</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Started the </span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1"> service.'</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>然后是停止命令的代码:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># stop.py</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">command</span><span class="p">(</span><span class="n">options</span><span class="p">):</span>
|
||
<span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
|
||
<span class="n">plural</span> <span class="o">=</span> <span class="s1">''</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="s1">'</span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1">'</span> <span class="o">%</span> <span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">plural</span> <span class="o">=</span> <span class="s1">'s'</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="s1">', '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">'</span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1">'</span> <span class="o">%</span> <span class="n">name</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">)</span>
|
||
<span class="n">i</span> <span class="o">=</span> <span class="n">services</span><span class="o">.</span><span class="n">rfind</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="n">services</span><span class="p">[:</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="s1">' and '</span> <span class="o">+</span> <span class="n">services</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">2</span><span class="p">:]</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'About to stop </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">services</span><span class="p">)</span>
|
||
<span class="c1"># actually do the command processing here ...</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Stopped the </span><span class="si">%s</span><span class="s1"> service</span><span class="si">%s</span><span class="s1">.'</span><span class="p">,</span> <span class="n">services</span><span class="p">,</span> <span class="n">plural</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>重启命令类似:</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># restart.py</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">command</span><span class="p">(</span><span class="n">options</span><span class="p">):</span>
|
||
<span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
|
||
<span class="n">plural</span> <span class="o">=</span> <span class="s1">''</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="s1">'</span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1">'</span> <span class="o">%</span> <span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">plural</span> <span class="o">=</span> <span class="s1">'s'</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="s1">', '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">'</span><span class="se">\'</span><span class="si">%s</span><span class="se">\'</span><span class="s1">'</span> <span class="o">%</span> <span class="n">name</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">names</span><span class="p">)</span>
|
||
<span class="n">i</span> <span class="o">=</span> <span class="n">services</span><span class="o">.</span><span class="n">rfind</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span>
|
||
<span class="n">services</span> <span class="o">=</span> <span class="n">services</span><span class="p">[:</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="s1">' and '</span> <span class="o">+</span> <span class="n">services</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">2</span><span class="p">:]</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'About to restart </span><span class="si">%s</span><span class="s1">'</span><span class="p">,</span> <span class="n">services</span><span class="p">)</span>
|
||
<span class="c1"># actually do the command processing here ...</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Restarted the </span><span class="si">%s</span><span class="s1"> service</span><span class="si">%s</span><span class="s1">.'</span><span class="p">,</span> <span class="n">services</span><span class="p">,</span> <span class="n">plural</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>如果以默认日志级别运行该程序,会得到以下结果:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>start<span class="w"> </span>foo
|
||
<span class="go">INFO start Started the 'foo' service.</span>
|
||
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>stop<span class="w"> </span>foo<span class="w"> </span>bar
|
||
<span class="go">INFO stop Stopped the 'foo' and 'bar' services.</span>
|
||
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>restart<span class="w"> </span>foo<span class="w"> </span>bar<span class="w"> </span>baz
|
||
<span class="go">INFO restart Restarted the 'foo', 'bar' and 'baz' services.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>第一个单词是日志级别,第二个单词是日志事件所在的模块或包的名称。</p>
|
||
<p>如果修改了日志级别,发送给日志的信息就能得以改变。如要显示更多信息,则可:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>DEBUG<span class="w"> </span>start<span class="w"> </span>foo
|
||
<span class="go">DEBUG start About to start foo</span>
|
||
<span class="go">INFO start Started the 'foo' service.</span>
|
||
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>DEBUG<span class="w"> </span>stop<span class="w"> </span>foo<span class="w"> </span>bar
|
||
<span class="go">DEBUG stop About to stop 'foo' and 'bar'</span>
|
||
<span class="go">INFO stop Stopped the 'foo' and 'bar' services.</span>
|
||
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>DEBUG<span class="w"> </span>restart<span class="w"> </span>foo<span class="w"> </span>bar<span class="w"> </span>baz
|
||
<span class="go">DEBUG restart About to restart 'foo', 'bar' and 'baz'</span>
|
||
<span class="go">INFO restart Restarted the 'foo', 'bar' and 'baz' services.</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>若要显示的信息少一些,则:</p>
|
||
<div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>WARNING<span class="w"> </span>start<span class="w"> </span>foo
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>WARNING<span class="w"> </span>stop<span class="w"> </span>foo<span class="w"> </span>bar
|
||
<span class="gp">$ </span>python<span class="w"> </span>app.py<span class="w"> </span>--log-level<span class="w"> </span>WARNING<span class="w"> </span>restart<span class="w"> </span>foo<span class="w"> </span>bar<span class="w"> </span>baz
|
||
</pre></div>
|
||
</div>
|
||
<p>这里的命令不会向控制台输出任何信息,因为没有记录 <code class="docutils literal notranslate"><span class="pre">WARNING</span></code> 以上级别的日志。</p>
|
||
</section>
|
||
<section id="a-qt-gui-for-logging">
|
||
<span id="qt-gui"></span><h2>Qt GUI 日志示例<a class="headerlink" href="#a-qt-gui-for-logging" title="永久链接至标题">¶</a></h2>
|
||
<p>GUI 应用程序如何记录日志,这是个常见的问题。 <a class="reference external" href="https://www.qt.io/">Qt</a> 框架是一个流行的跨平台 UI 框架,采用的是 <a class="reference external" href="https://pypi.org/project/PySide2/">PySide2</a> 或 <a class="reference external" href="https://pypi.org/project/PyQt5/">PyQt5</a> 库。</p>
|
||
<p>下面的例子演示了将日志写入 Qt GUI 程序的过程。这里引入了一个简单的 <code class="docutils literal notranslate"><span class="pre">QtHandler</span></code> 类,参数是一个可调用对象,其应为嵌入主线程某个“槽位”中运行的,因为GUI 的更新由主线程完成。这里还创建了一个工作线程,以便演示由 UI(通过人工点击日志按钮)和后台工作线程(此处只是记录级别和时间间隔均随机生成的日志信息)将日志写入 GUI 的过程。</p>
|
||
<p>该工作线程是用 Qt 的 <code class="docutils literal notranslate"><span class="pre">QThread</span></code> 类实现的,而不是 <a class="reference internal" href="../library/threading.html#module-threading" title="threading: Thread-based parallelism."><code class="xref py py-mod docutils literal notranslate"><span class="pre">threading</span></code></a> 模块,因为某些情况下只能采用 <code class="docutils literal notranslate"><span class="pre">`QThread</span></code>,它与其他 <code class="docutils literal notranslate"><span class="pre">Qt</span></code> 组件的集成性更好一些。</p>
|
||
<p>以下代码应能适用于最新版的 <code class="docutils literal notranslate"><span class="pre">PySide2</span></code> 或 <code class="docutils literal notranslate"><span class="pre">PyQt5</span></code>。对于低版本的 Qt 应该也能适用。更多详情,请参阅代码注释。</p>
|
||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">datetime</span>
|
||
<span class="kn">import</span> <span class="nn">logging</span>
|
||
<span class="kn">import</span> <span class="nn">random</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
|
||
<span class="c1"># Deal with minor differences between PySide2 and PyQt5</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="kn">from</span> <span class="nn">PySide2</span> <span class="kn">import</span> <span class="n">QtCore</span><span class="p">,</span> <span class="n">QtGui</span><span class="p">,</span> <span class="n">QtWidgets</span>
|
||
<span class="n">Signal</span> <span class="o">=</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">Signal</span>
|
||
<span class="n">Slot</span> <span class="o">=</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">Slot</span>
|
||
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
|
||
<span class="kn">from</span> <span class="nn">PyQt5</span> <span class="kn">import</span> <span class="n">QtCore</span><span class="p">,</span> <span class="n">QtGui</span><span class="p">,</span> <span class="n">QtWidgets</span>
|
||
<span class="n">Signal</span> <span class="o">=</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">pyqtSignal</span>
|
||
<span class="n">Slot</span> <span class="o">=</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">pyqtSlot</span>
|
||
|
||
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Signals need to be contained in a QObject or subclass in order to be correctly</span>
|
||
<span class="c1"># initialized.</span>
|
||
<span class="c1">#</span>
|
||
<span class="k">class</span> <span class="nc">Signaller</span><span class="p">(</span><span class="n">QtCore</span><span class="o">.</span><span class="n">QObject</span><span class="p">):</span>
|
||
<span class="n">signal</span> <span class="o">=</span> <span class="n">Signal</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">LogRecord</span><span class="p">)</span>
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Output to a Qt GUI is only supposed to happen on the main thread. So, this</span>
|
||
<span class="c1"># handler is designed to take a slot function which is set up to run in the main</span>
|
||
<span class="c1"># thread. In this example, the function takes a string argument which is a</span>
|
||
<span class="c1"># formatted log message, and the log record which generated it. The formatted</span>
|
||
<span class="c1"># string is just a convenience - you could format a string for output any way</span>
|
||
<span class="c1"># you like in the slot function itself.</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># You specify the slot function to do whatever GUI updates you want. The handler</span>
|
||
<span class="c1"># doesn't know or care about specific UI elements.</span>
|
||
<span class="c1">#</span>
|
||
<span class="k">class</span> <span class="nc">QtHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">Handler</span><span class="p">):</span>
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">slotfunc</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">signaller</span> <span class="o">=</span> <span class="n">Signaller</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">signaller</span><span class="o">.</span><span class="n">signal</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">slotfunc</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">emit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">signaller</span><span class="o">.</span><span class="n">signal</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">record</span><span class="p">)</span>
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># This example uses QThreads, which means that the threads at the Python level</span>
|
||
<span class="c1"># are named something like "Dummy-1". The function below gets the Qt name of the</span>
|
||
<span class="c1"># current thread.</span>
|
||
<span class="c1">#</span>
|
||
<span class="k">def</span> <span class="nf">ctname</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">QThread</span><span class="o">.</span><span class="n">currentThread</span><span class="p">()</span><span class="o">.</span><span class="n">objectName</span><span class="p">()</span>
|
||
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Used to generate random levels for logging.</span>
|
||
<span class="c1">#</span>
|
||
<span class="n">LEVELS</span> <span class="o">=</span> <span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">)</span>
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># This worker class represents work that is done in a thread separate to the</span>
|
||
<span class="c1"># main thread. The way the thread is kicked off to do work is via a button press</span>
|
||
<span class="c1"># that connects to a slot in the worker.</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Because the default threadName value in the LogRecord isn't much use, we add</span>
|
||
<span class="c1"># a qThreadName which contains the QThread name as computed above, and pass that</span>
|
||
<span class="c1"># value in an "extra" dictionary which is used to update the LogRecord with the</span>
|
||
<span class="c1"># QThread name.</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># This example worker just outputs messages sequentially, interspersed with</span>
|
||
<span class="c1"># random delays of the order of a few seconds.</span>
|
||
<span class="c1">#</span>
|
||
<span class="k">class</span> <span class="nc">Worker</span><span class="p">(</span><span class="n">QtCore</span><span class="o">.</span><span class="n">QObject</span><span class="p">):</span>
|
||
<span class="nd">@Slot</span><span class="p">()</span>
|
||
<span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">extra</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'qThreadName'</span><span class="p">:</span> <span class="n">ctname</span><span class="p">()</span> <span class="p">}</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Started work'</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="n">extra</span><span class="p">)</span>
|
||
<span class="n">i</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="c1"># Let the thread run until interrupted. This allows reasonably clean</span>
|
||
<span class="c1"># thread termination.</span>
|
||
<span class="k">while</span> <span class="ow">not</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">QThread</span><span class="o">.</span><span class="n">currentThread</span><span class="p">()</span><span class="o">.</span><span class="n">isInterruptionRequested</span><span class="p">():</span>
|
||
<span class="n">delay</span> <span class="o">=</span> <span class="mf">0.5</span> <span class="o">+</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
|
||
<span class="n">level</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">LEVELS</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="s1">'Message after delay of </span><span class="si">%3.1f</span><span class="s1">: </span><span class="si">%d</span><span class="s1">'</span><span class="p">,</span> <span class="n">delay</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="n">extra</span><span class="p">)</span>
|
||
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
|
||
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Implement a simple UI for this cookbook example. This contains:</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># * A read-only text edit window which holds formatted log messages</span>
|
||
<span class="c1"># * A button to start work and log stuff in a separate thread</span>
|
||
<span class="c1"># * A button to log something from the main thread</span>
|
||
<span class="c1"># * A button to clear the log window</span>
|
||
<span class="c1">#</span>
|
||
<span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QtWidgets</span><span class="o">.</span><span class="n">QWidget</span><span class="p">):</span>
|
||
|
||
<span class="n">COLORS</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">:</span> <span class="s1">'black'</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">:</span> <span class="s1">'blue'</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">:</span> <span class="s1">'orange'</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">:</span> <span class="s1">'red'</span><span class="p">,</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">:</span> <span class="s1">'purple'</span><span class="p">,</span>
|
||
<span class="p">}</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">app</span> <span class="o">=</span> <span class="n">app</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">textedit</span> <span class="o">=</span> <span class="n">te</span> <span class="o">=</span> <span class="n">QtWidgets</span><span class="o">.</span><span class="n">QPlainTextEdit</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
|
||
<span class="c1"># Set whatever the default monospace font is for the platform</span>
|
||
<span class="n">f</span> <span class="o">=</span> <span class="n">QtGui</span><span class="o">.</span><span class="n">QFont</span><span class="p">(</span><span class="s1">'nosuchfont'</span><span class="p">)</span>
|
||
<span class="n">f</span><span class="o">.</span><span class="n">setStyleHint</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">Monospace</span><span class="p">)</span>
|
||
<span class="n">te</span><span class="o">.</span><span class="n">setFont</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="n">te</span><span class="o">.</span><span class="n">setReadOnly</span><span class="p">(</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="n">PB</span> <span class="o">=</span> <span class="n">QtWidgets</span><span class="o">.</span><span class="n">QPushButton</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">work_button</span> <span class="o">=</span> <span class="n">PB</span><span class="p">(</span><span class="s1">'Start background work'</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">log_button</span> <span class="o">=</span> <span class="n">PB</span><span class="p">(</span><span class="s1">'Log a message at a random level'</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">clear_button</span> <span class="o">=</span> <span class="n">PB</span><span class="p">(</span><span class="s1">'Clear log window'</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handler</span> <span class="o">=</span> <span class="n">h</span> <span class="o">=</span> <span class="n">QtHandler</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">update_status</span><span class="p">)</span>
|
||
<span class="c1"># Remember to use qThreadName rather than threadName in the format string.</span>
|
||
<span class="n">fs</span> <span class="o">=</span> <span class="s1">'</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(qThreadName)-12s</span><span class="s1"> </span><span class="si">%(levelname)-8s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">'</span>
|
||
<span class="n">formatter</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">Formatter</span><span class="p">(</span><span class="n">fs</span><span class="p">)</span>
|
||
<span class="n">h</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">formatter</span><span class="p">)</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">h</span><span class="p">)</span>
|
||
<span class="c1"># Set up to terminate the QThread when we exit</span>
|
||
<span class="n">app</span><span class="o">.</span><span class="n">aboutToQuit</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">force_quit</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Lay out all the widgets</span>
|
||
<span class="n">layout</span> <span class="o">=</span> <span class="n">QtWidgets</span><span class="o">.</span><span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
|
||
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">te</span><span class="p">)</span>
|
||
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">work_button</span><span class="p">)</span>
|
||
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">log_button</span><span class="p">)</span>
|
||
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">clear_button</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">setFixedSize</span><span class="p">(</span><span class="mi">900</span><span class="p">,</span> <span class="mi">400</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Connect the non-worker slots and signals</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">log_button</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">manual_update</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">clear_button</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">clear_display</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Start a new worker thread and connect the slots for the worker</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">start_thread</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">work_button</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">worker</span><span class="o">.</span><span class="n">start</span><span class="p">)</span>
|
||
<span class="c1"># Once started, the button should be disabled</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">work_button</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="k">lambda</span> <span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">work_button</span><span class="o">.</span><span class="n">setEnabled</span><span class="p">(</span><span class="kc">False</span><span class="p">))</span>
|
||
|
||
<span class="k">def</span> <span class="nf">start_thread</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker</span> <span class="o">=</span> <span class="n">Worker</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span> <span class="o">=</span> <span class="n">QtCore</span><span class="o">.</span><span class="n">QThread</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker</span><span class="o">.</span><span class="n">setObjectName</span><span class="p">(</span><span class="s1">'Worker'</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">setObjectName</span><span class="p">(</span><span class="s1">'WorkerThread'</span><span class="p">)</span> <span class="c1"># for qThreadName</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker</span><span class="o">.</span><span class="n">moveToThread</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="p">)</span>
|
||
<span class="c1"># This will start an event loop in the worker thread</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">kill_thread</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="c1"># Just tell the worker to stop, then tell it to quit and wait for that</span>
|
||
<span class="c1"># to happen</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">requestInterruption</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">isRunning</span><span class="p">():</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s1">'worker has already exited.'</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">force_quit</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="c1"># For use when the window is closed</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">worker_thread</span><span class="o">.</span><span class="n">isRunning</span><span class="p">():</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">kill_thread</span><span class="p">()</span>
|
||
|
||
<span class="c1"># The functions below update the UI and run in the main thread because</span>
|
||
<span class="c1"># that's where the slots are set up</span>
|
||
|
||
<span class="nd">@Slot</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">logging</span><span class="o">.</span><span class="n">LogRecord</span><span class="p">)</span>
|
||
<span class="k">def</span> <span class="nf">update_status</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
|
||
<span class="n">color</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">COLORS</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">record</span><span class="o">.</span><span class="n">levelno</span><span class="p">,</span> <span class="s1">'black'</span><span class="p">)</span>
|
||
<span class="n">s</span> <span class="o">=</span> <span class="s1">'<pre><font color="</span><span class="si">%s</span><span class="s1">"></span><span class="si">%s</span><span class="s1"></font></pre>'</span> <span class="o">%</span> <span class="p">(</span><span class="n">color</span><span class="p">,</span> <span class="n">status</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">textedit</span><span class="o">.</span><span class="n">appendHtml</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
|
||
|
||
<span class="nd">@Slot</span><span class="p">()</span>
|
||
<span class="k">def</span> <span class="nf">manual_update</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="c1"># This function uses the formatted message passed in, but also uses</span>
|
||
<span class="c1"># information from the record to format the message in an appropriate</span>
|
||
<span class="c1"># color according to its severity (level).</span>
|
||
<span class="n">level</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">LEVELS</span><span class="p">)</span>
|
||
<span class="n">extra</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'qThreadName'</span><span class="p">:</span> <span class="n">ctname</span><span class="p">()</span> <span class="p">}</span>
|
||
<span class="n">logger</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="s1">'Manually logged!'</span><span class="p">,</span> <span class="n">extra</span><span class="o">=</span><span class="n">extra</span><span class="p">)</span>
|
||
|
||
<span class="nd">@Slot</span><span class="p">()</span>
|
||
<span class="k">def</span> <span class="nf">clear_display</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">textedit</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
|
||
|
||
|
||
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
|
||
<span class="n">QtCore</span><span class="o">.</span><span class="n">QThread</span><span class="o">.</span><span class="n">currentThread</span><span class="p">()</span><span class="o">.</span><span class="n">setObjectName</span><span class="p">(</span><span class="s1">'MainThread'</span><span class="p">)</span>
|
||
<span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
|
||
<span class="n">app</span> <span class="o">=</span> <span class="n">QtWidgets</span><span class="o">.</span><span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
|
||
<span class="n">example</span> <span class="o">=</span> <span class="n">Window</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
|
||
<span class="n">example</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">())</span>
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span><span class="o">==</span><span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="n">main</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||
<div class="sphinxsidebarwrapper">
|
||
<h3><a href="../contents.html">目录</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">日志专题手册</a><ul>
|
||
<li><a class="reference internal" href="#using-logging-in-multiple-modules">在多模块中使用日志</a></li>
|
||
<li><a class="reference internal" href="#logging-from-multiple-threads">在多个线程中记录日志</a></li>
|
||
<li><a class="reference internal" href="#multiple-handlers-and-formatters">多个 handler 和多种 formatter</a></li>
|
||
<li><a class="reference internal" href="#logging-to-multiple-destinations">在多个地方记录日志</a></li>
|
||
<li><a class="reference internal" href="#configuration-server-example">日志配置服务器示例</a></li>
|
||
<li><a class="reference internal" href="#dealing-with-handlers-that-block">处理日志 handler 的阻塞</a></li>
|
||
<li><a class="reference internal" href="#sending-and-receiving-logging-events-across-a-network">通过网络收发日志事件</a></li>
|
||
<li><a class="reference internal" href="#adding-contextual-information-to-your-logging-output">在自己的输出日志中添加上下文信息</a><ul>
|
||
<li><a class="reference internal" href="#using-loggeradapters-to-impart-contextual-information">利用 LoggerAdapter 传递上下文信息</a><ul>
|
||
<li><a class="reference internal" href="#using-objects-other-than-dicts-to-pass-contextual-information">使用除字典之外的其它对象传递上下文信息</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#using-filters-to-impart-contextual-information">使用过滤器传递上下文信息</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#logging-to-a-single-file-from-multiple-processes">从多个进程记录至单个文件</a><ul>
|
||
<li><a class="reference internal" href="#using-concurrent-futures-processpoolexecutor">concurrent.futures.ProcessPoolExecutor 的用法</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#using-file-rotation">轮换日志文件</a></li>
|
||
<li><a class="reference internal" href="#use-of-alternative-formatting-styles">使用其他日志格式化方式</a></li>
|
||
<li><a class="reference internal" href="#customizing-logrecord">自定义 <code class="docutils literal notranslate"><span class="pre">LogRecord</span></code></a></li>
|
||
<li><a class="reference internal" href="#subclassing-queuehandler-a-zeromq-example">子类化 QueueHandler - ZeroMQ 示例</a></li>
|
||
<li><a class="reference internal" href="#subclassing-queuelistener-a-zeromq-example">子类化 QueueListener —— ZeroMQ 示例</a></li>
|
||
<li><a class="reference internal" href="#an-example-dictionary-based-configuration">基于字典进行日志配置的示例</a></li>
|
||
<li><a class="reference internal" href="#using-a-rotator-and-namer-to-customize-log-rotation-processing">利用 rotator 和 namer 自定义日志轮换操作</a></li>
|
||
<li><a class="reference internal" href="#a-more-elaborate-multiprocessing-example">更加详细的多道处理示例</a></li>
|
||
<li><a class="reference internal" href="#inserting-a-bom-into-messages-sent-to-a-sysloghandler">在发送给 SysLogHandler 的信息中插入一个 BOM。</a></li>
|
||
<li><a class="reference internal" href="#implementing-structured-logging">结构化日志的实现代码</a></li>
|
||
<li><a class="reference internal" href="#customizing-handlers-with-dictconfig">利用 <code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code> 自定义 handler</a></li>
|
||
<li><a class="reference internal" href="#using-particular-formatting-styles-throughout-your-application">生效于整个应用程序的格式化样式</a><ul>
|
||
<li><a class="reference internal" href="#using-logrecord-factories">LogRecord 工厂的用法</a></li>
|
||
<li><a class="reference internal" href="#using-custom-message-objects">自定义信息对象的使用</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#configuring-filters-with-dictconfig">利用 <code class="xref py py-func docutils literal notranslate"><span class="pre">dictConfig()</span></code> 定义过滤器</a></li>
|
||
<li><a class="reference internal" href="#customized-exception-formatting">异常信息的自定义格式化</a></li>
|
||
<li><a class="reference internal" href="#speaking-logging-messages">语音播报日志信息</a></li>
|
||
<li><a class="reference internal" href="#buffering-logging-messages-and-outputting-them-conditionally">缓冲日志消息并有条件地输出它们</a></li>
|
||
<li><a class="reference internal" href="#formatting-times-using-utc-gmt-via-configuration">通过配置使用UTC (GMT) 格式化时间</a></li>
|
||
<li><a class="reference internal" href="#using-a-context-manager-for-selective-logging">使用上下文管理器的可选的日志记录</a></li>
|
||
<li><a class="reference internal" href="#a-cli-application-starter-template">命令行日志应用起步</a></li>
|
||
<li><a class="reference internal" href="#a-qt-gui-for-logging">Qt GUI 日志示例</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<h4>上一个主题</h4>
|
||
<p class="topless"><a href="logging.html"
|
||
title="上一章">日志指南</a></p>
|
||
<h4>下一个主题</h4>
|
||
<p class="topless"><a href="regex.html"
|
||
title="下一章">正则表达式指南</a></p>
|
||
<div role="note" aria-label="source link">
|
||
<h3>本页</h3>
|
||
<ul class="this-page-menu">
|
||
<li><a href="../bugs.html">报告 Bug</a></li>
|
||
<li>
|
||
<a href="https://github.com/python/cpython/blob/3.8/Doc/howto/logging-cookbook.rst"
|
||
rel="nofollow">显示源代码
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="related" role="navigation" aria-label="related navigation">
|
||
<h3>导航</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="总目录"
|
||
>索引</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python 模块索引"
|
||
>模块</a> |</li>
|
||
<li class="right" >
|
||
<a href="regex.html" title="正则表达式指南"
|
||
>下一页</a> |</li>
|
||
<li class="right" >
|
||
<a href="logging.html" title="日志指南"
|
||
>上一页</a> |</li>
|
||
|
||
<li><img src="../_static/py.svg" alt="python logo" style="vertical-align: middle; margin-top: -1px"/></li>
|
||
<li><a href="https://www.python.org/">Python</a> »</li>
|
||
<li class="switchers">
|
||
<div class="language_switcher_placeholder"></div>
|
||
<div class="version_switcher_placeholder"></div>
|
||
</li>
|
||
<li>
|
||
|
||
</li>
|
||
<li id="cpython-language-and-version">
|
||
<a href="../index.html">3.8.20 Documentation</a> »
|
||
</li>
|
||
|
||
<li class="nav-item nav-item-1"><a href="index.html" >Python 指南</a> »</li>
|
||
<li class="right">
|
||
|
||
|
||
<div class="inline-search" role="search">
|
||
<form class="inline-search" action="../search.html" method="get">
|
||
<input placeholder="快速搜索" aria-label="快速搜索" type="text" name="q" />
|
||
<input type="submit" value="转向" />
|
||
<input type="hidden" name="check_keywords" value="yes" />
|
||
<input type="hidden" name="area" value="default" />
|
||
</form>
|
||
</div>
|
||
|
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
<div class="footer">
|
||
© <a href="../copyright.html">版权所有</a> 2001-2024, Python Software Foundation.
|
||
<br />
|
||
This page is licensed under the Python Software Foundation License Version 2.
|
||
<br />
|
||
Examples, recipes, and other code in the documentation are additionally licensed under the Zero Clause BSD License.
|
||
<br />
|
||
|
||
<br />
|
||
|
||
The Python Software Foundation is a non-profit corporation.
|
||
<a href="https://www.python.org/psf/donations/">Please donate.</a>
|
||
<br />
|
||
<br />
|
||
|
||
最后更新于 12月 09, 2024.
|
||
<a href="https://docs.python.org/3/bugs.html">Found a bug</a>?
|
||
<br />
|
||
|
||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 2.4.4.
|
||
</div>
|
||
|
||
</body>
|
||
</html> |