Python 日志指南
如果处理得当,日志将成为可观察性套件的重要组成部分。日志记录了应用程序中数据的变化情况。它们可以帮助您解答诸如此类的问题:为什么 John Doe 昨天结账时收到错误信息?他提供了哪些输入?
日志记录的实践范围很广,从非常简单的静态字符串语句到丰富的结构化数据事件。在本文中,我们将探讨如何在 Python 中使用日志记录来更好地了解应用程序的运行情况。此外,我还将探讨一些最佳实践,帮助您最大限度地利用日志的价值。
print('为什么不直接使用打印语句?')
许多 Python 教程都会向读者展示如何使用它print
作为调试工具。以下是一个使用print
脚本打印异常的示例:
>>> import sys
>>>
>>> def captureException():
... return sys.exc_info()
...
>>> try:
... 1/0
... except:
... print(captureException())
...
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero',), <traceback object at 0x101acb1c8>)
None
虽然这在较小的脚本中很有用,但随着应用程序和操作需求的增长,print
它就不再是一个可行的解决方案。它无法提供关闭所有输出语句类别的灵活性,并且只允许您将输出输出到stdout
。它还会丢失诸如行号和生成时间等有助于调试的信息。虽然print
这是最简单的方法,因为它不需要设置,但它很快就会给您带来麻烦。发送直接打印到的包也是不好的做法,stdout
因为它剥夺了用户控制消息的能力。
logging.info(“Hello World to Logging”)
logging 模块是 Python 标准库的一部分,自 2.3 版本起就已存在。它会自动将上下文信息(例如行号和时间戳)添加到日志中。该模块可以轻松添加命名空间日志和严重性级别,让您更好地控制输出位置和内容。
我相信学习的最好方法就是实践,所以我鼓励你在 Python REPL 中学习。日志模块的入门很简单,你只需要做以下几件事:
>>> import logging
>>> logging.basicConfig()
>>> logger = logging.getLogger(__name__)
>>>
>>> logger.critical('logging is easier than I was expecting')
CRITICAL:__main__:logging is easier than I was expecting
刚才发生了什么? getLogger
为我们提供了一个记录器实例。然后我们给它添加了事件“日志记录比我预期的要容易”,级别为critical
。
级别
Python 模块允许您根据事件的严重程度来区分它们。级别以 0 到 50 之间的整数表示。该模块定义了五个涵盖所有级别的常量,方便您轻松区分不同的消息。
每个级别都有其含义,您应该认真思考您所登录的级别。
>>> logger.critical("this better be bad")
CRITICAL:root:this better be bad
>>> logger.error("more serious problem")
ERROR:root:more serious problem
>>> logger.warning("an unexpected event")
WARNING:root:an unexpected event
>>> logger.info("show user flow through program")
>>> logger.debug("used to track variables when coding")
请注意,info
和debug
并没有打印任何消息。默认情况下,记录器只会打印warning
、error
或critical
消息。您可以自定义此行为,甚至可以在运行时修改它以动态激活更详细的日志记录。
>>> # should show up; info is a higher level than debug
>>> logger.setLevel(logging.DEBUG)
>>> logger.info(1)
INFO:root:1
>>>
>>> # shouldn't show up; info is a lower level than warning
... logger.setLevel(logging.WARNING)
>>> logger.info(2)
格式化日志
默认格式化程序对于格式化日志没什么用,因为它不包含关键信息。日志模块可以让你通过更改格式轻松添加这些信息。
我们将设置显示时间、级别和消息的格式:
>>> import logging
>>> logFormatter = '%(asctime)s - %(levelname)s - %(message)s'
>>> logging.basicConfig(format=logFormatter, level=logging.DEBUG)
>>> logger = logging.getLogger(__name__)
>>> logger.info("test")
2018-06-19 17:42:38,134 - INFO - test
其中一些数据(例如time
和)levelname
可以自动捕获,但您可以(并且应该)将extra
上下文推送到您的日志中。
添加上下文
普通的日志消息提供的信息几乎和没有日志消息一样少。想象一下,如果你查看日志,却看到了…… removed from cart
。这会让你很难回答诸如:该项目是什么时候被移除的?是谁移除的?他们移除了什么?
最好将结构化数据添加到日志中,而不是将对象字符串化来丰富它。如果没有结构化数据,将来很难解读日志流。解决这个问题的最佳方法是将重要的元数据推送到对象extra
。这样,您就可以丰富流中的消息。
>>> import logging
>>> logFormatter = '%(asctime)s - %(user)s - %(levelname)s - %(message)s'
>>> logging.basicConfig(format=logFormatter, level=logging.DEBUG)
>>> logger = logging.getLogger(__name__)
>>> logger.info('purchase completed', extra={'user': 'Sid Panjwani'})
2018-06-19 17:44:10,276 - Sid Panjwani - INFO - purchase completed
表现
日志记录会带来开销,需要与您编写的软件的性能要求进行平衡。虽然开销通常可以忽略不计,但不良做法和错误可能会导致不幸的情况。
以下是一些有用的提示:
将昂贵的调用包装在级别检查中
Python日志文档建议将昂贵的调用包装在级别检查中以延迟评估。
if logger.isEnabledFor(logging.INFO):
logger.debug('%s', expensive_func())
现在,expensive_func
仅当日志记录级别大于或等于时才会被调用INFO
。
避免在热路径中记录日志
热路径是性能至关重要的代码,因此经常执行。最好避免在此处记录日志,因为它可能会成为 IO 瓶颈,除非由于热路径之外无法获取要记录的数据而不得不记录日志。
存储和访问这些日志
既然您已经学会了如何编写这些(精美的)日志,接下来就需要确定如何处理它们了。默认情况下,日志会写入标准输出设备(可能是您的终端窗口),但 Python 的日志模块提供了丰富的选项来自定义输出处理。我们鼓励将日志记录到标准输出,Heroku、Amazon Elastic Beanstalk 和 Docker 等平台都基于此标准构建,它们会捕获标准输出并在平台级别重定向到其他日志记录工具。
记录到文件
日志模块可以轻松地使用“处理程序”将日志写入本地文件以便长期保留。
>>> import logging
>>> logger = logging.getLogger(__name__)
>>>
>>> handler = logging.FileHandler('myLogs.log')
>>> handler.setLevel(logging.INFO)
>>>
>>> logger.addHandler(handler)
>>> logger.info('You can find this written in myLogs.log')
如果你一直在关注(你应该这样做),你会注意到你的日志不会显示在文件中,因为它的级别是info
。确保使用setLevel
来更改它。
现在可以使用 轻松搜索日志文件grep
。
grep critical myLogs.log
现在您可以搜索包含关键字(例如critical
或warning
)的消息。
旋转原木
Python 日志模块可以轻松地在一段时间后或日志文件达到一定大小后将日志记录到不同的文件中。如果您希望自动删除较旧的日志,或者希望按日期搜索日志,此功能非常有用,因为您无需在庞大的文件中搜索已分组的日志集合。
要创建一个handler
每天创建新日志文件并自动删除超过五天的日志的日志文件,您可以使用TimedRotatingFileHandler
。以下是示例:
logger = logging.getLogger('Rotating Log by Day')
# writes to pathToLog
# creates a new file every day because `when="d"` and `interval=1`
# automatically deletes logs more than 5 days old because `backupCount=5`
handler = TimedRotatingFileHandler(pathToLog, when="d", interval=1, backupCount=5)
缺点
在观察应用程序时,了解日志的作用至关重要。我建议读者了解一下可观察性原则的三大支柱,其中指出日志应该是观察应用程序时可以使用的少数工具之一。日志是可观察性堆栈的组成部分,但指标和跟踪同样如此。
日志记录可能会很昂贵,尤其是在规模化的情况下。指标可用于汇总数据并回答有关客户的问题,而无需保留每个操作的痕迹,而跟踪功能则可让您查看整个平台中请求的路径。
总结
如果说这篇文章有什么值得借鉴的地方,那就是日志是用户操作的真实来源。即使是像将商品放入购物车或从购物车中取出这样短暂的操作,追踪用户的操作步骤对于提交错误报告也至关重要,而日志可以帮你了解他们的操作。
Python 日志模块让这一切变得简单。它允许你格式化日志,使用级别动态区分消息,并使用“处理程序”将日志发送到外部。虽然它并非洞察用户操作的唯一机制,但它是捕获原始事件数据并解答未知问题的有效方法。
无需自己动手,我们这里有一个非常棒的服务 @ Timber(真的很棒),可以自动捕获日志中的上下文,让调试更加轻松。完全免费试用,甚至不需要信用卡!
登录到云端
将日志写入托管日志聚合服务确实可以让您的生活更轻松,因此您可以将时间集中在重要的事情上。
免责声明:我是 Timber 的现任员工。本节内容完全可选,但我真诚地相信登录云端将使您的生活更加轻松(而且您可以完全免费试用)。
pip install timber
import logging
import timber
logger = logging.getLogger(__name__)
timber_handler = timber.TimberHandler(api_key='...')
logger.addHandler(timber_handler)
就是这样。您只需从timber.io获取 API 密钥,即可查看日志。我们会自动从日志模块捕获日志,因此您可以继续遵循最佳实践并正常记录,同时我们会无缝添加上下文。
注意:如果您在 Flask 上创建应用程序,我建议您也查看他们的日志记录文档。
鏂囩珷鏉ユ簮锛�https://dev.to/timber/the-pythonic-guide-to-logging-4p35