记录与 print() + 记录最佳实践的优势
Posted
技术标签:
【中文标题】记录与 print() + 记录最佳实践的优势【英文标题】:Advantages of logging vs. print() + logging best practices 【发布时间】:2012-05-27 12:13:05 【问题描述】:我目前正在开发 pyftpdlib 模块的 1.0.0 版本。 这个新版本将引入一些向后不兼容的更改 某些 API 将不再接受字节,而是接受 unicode。 当我在做这件事时,作为这个破坏的一部分,我正在考虑 摆脱我的日志记录功能的可能性,这些功能目前使用 打印语句,并改用日志模块。
截至目前,pyftpdlib 将日志记录委托给 3 个函数:
def log(s):
"""Log messages intended for the end user."""
print s
def logline(s):
"""Log commands and responses passing through the command channel."""
print s
def logerror(s):
"""Log traceback outputs occurring in case of errors."""
print >> sys.stderr, s
愿意自定义日志(例如将它们写入文件)的用户是 应该只覆盖这 3 个函数,如下所示:
>>> from pyftpdlib import ftpserver
>>>
>>> def log2file(s):
... open('ftpd.log', 'a').write(s)
...
>>> ftpserver.log = ftpserver.logline = ftpserver.logerror = log2file
现在我想知道:摆脱这种方法有什么好处 而是使用日志记录模块? 从模块供应商的角度来看,我到底应该怎么做 在我的模块中公开日志记录功能? 我应该这样做吗:
import logging
logger = logging.getLogger("pyftpdlib")
...并在我的文档中声明“记录器”是假定的对象 在用户想要自定义日志的行为方式时使用? 故意设置预定义格式输出是否合法:
FORMAT = '[%(asctime)] %(message)s'
logging.basicConfig(format=FORMAT)
logger = logging.getLogger('pyftpdlib')
...?
你能想到一个第三方模块吗?我可以从日志功能作为公共 API 的一部分公开和整合的地方获取线索?
提前致谢。
【问题讨论】:
【参考方案1】:这是我用来制作可自定义logger 的资源。我没有做太多改动,只是添加了一个 if 语句,并传入我是要登录到文件还是只登录到控制台。
检查this 着色器。对输出进行着色非常好,因此 DEBUG 看起来与 WARN 不同,而 WARN 看起来与 INFO 不同。
Logging 模块捆绑了很多不错的功能,例如 SMTP 日志记录、文件轮换日志记录(因此您可以保存几个旧日志文件,但每次出现问题时都不会创建 100 个)。
如果您想迁移到 Python 3,使用日志记录模块将无需更改打印语句。
日志记录非常棒,具体取决于您在做什么,我之前只是轻轻地使用它来查看我在程序中的位置(如果您正在运行此功能,请以这种方式着色),但它的功能要强大得多而不是常规的打印语句。
【讨论】:
【参考方案2】:您可以查看 Django(只需创建一个示例项目)并查看它是如何初始化 logger 子系统的。
还有一个contextual logger helper,这是我前段时间写的——这个记录器会自动获取模块/类/函数的名称,是从中初始化的。这对于调试消息非常有用,您可以直接看到该模块吐出的消息以及调用流程是如何进行的。
【讨论】:
【参考方案3】:库(ftp 服务器或客户端库)不应该初始化日志系统。 所以可以实例化一个记录器对象并指向 logging.basicConfig 中的 文档(或提供具有更高级输出的 basicConfig 功能 并让用户在他的日志配置策略中进行选择,简单的 basicConfig 或 库提供的配置)
框架(例如 django)或服务器(ftp 服务器守护进程) 应将日志系统初始化为合理的 默认并允许自定义日志系统配置。
【讨论】:
【参考方案4】:通常库应该只创建一个NullHandler 处理程序,它只是一个什么都不做的处理程序。然后,使用您的库的最终用户或应用程序开发人员可以配置日志记录系统。有关详细信息,请参阅 logging
文档中的 Configuring Logging for a Library 部分。特别是,请参阅开头的注释
强烈建议您不要将
NullHandler
以外的任何处理程序添加到库的记录器中。
在您的情况下,我将根据日志记录文档简单地创建一个日志记录处理程序,
import logging
logging.getLogger('pyftpdlib').addHandler(logging.NullHandler())
编辑 问题中概述的日志记录实现似乎完全合理。在您的文档中只需提及logger
并讨论或将用户指向logging.setLevel
和logging.setFormatter
方法来自定义库的输出。除了使用logging.basicConfig(format=FORMAT)
,您还可以考虑使用logging.config.fileConfig
来管理输出设置并在文档中的某处记录配置文件,再次将用户指向该文件中预期格式的日志记录模块文档。
【讨论】:
这是否意味着默认情况下不会产生输出?在那种情况下,我对此不满意。 默认情况下不会产生任何输出。此外,logging.NullHandler
仅适用于 Python 2.7(我认为是 > 3.2),这可能是个问题。如果您只使用logger = logging.getLogger('pyftpdlib')
,默认情况下您将收到消息,您可以将您的用户指向logging.setLevel
和logging.setFormatter
文档以演示如何为您的库配置输出消息。另外,您可以使用文件配置logging.config.fileConfig
允许用户配置记录器
另外,为了回答您的一个原始问题,PEP 282 中简要提到了使用日志记录而不是打印语句的好处:如果标准库中包含单个日志记录机制, 1) 日志记录更有可能做得“很好”,并且 2) 多个库将能够集成到更大的应用程序中,这些应用程序可以合理地连贯地记录。
关于我对默认情况下不生成任何输出这一事实的担忧的最新更新。这是基于 user 应该在自己的代码中调用 logging.basicConfig() 的假设,如果他/她忘记这样做,则不会提供日志记录。我通过在模块的“start()”方法中添加这个“如果不是 logging.getLogger().handlers:logging.basicConfig()”来解决这个问题。这样,即使用户没有配置日志记录,模块也会决定无论如何都要这样做以生成日志。以上是关于记录与 print() + 记录最佳实践的优势的主要内容,如果未能解决你的问题,请参考以下文章