原文:
towardsdatascience.com/log-breadcrumbs-only-show-logs-leading-up-to-an-error-82b9f4c15520
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a40ff83722411f873098d942f36a4432.png
图片由 Daniel Tseng / Unsplash 提供
在这篇文章中,我们将探讨一种高效记录日志碎片的途径,只显示导致错误的日志。我们将仅使用 Python 的标准日志库来创建一个高效的日志设置,仅在发生异常时捕获调试日志。这种方法提供了对导致问题的步骤的详细视图,同时减少混乱和最小化 I/O。让我们开始编码!
为什么记录日志碎片?
当发生错误时,你希望尽可能多地拥有信息,以便引导你找到代码中的问题。记录大量信息在这方面非常有用。
坏处是,所有这些日志都需要被处理。然后需要将它们写入文件或通过 HTTP 发送到端点,这可能会影响你的应用程序或服务器的性能。此外,它可能会使日志变得混乱,使得在发生错误时更难找到相关信息。
面包屑方法“忽略”例如所有debug日志,除非发生错误。这允许你记录大量关于错误的详细信息,同时保持性能和概览在水平。
设置面包屑路径
下面是一个简单的函数,divide,其中包含帮助跟踪其内部行为的调试日志。理想情况下,我们不想每次都看到日志,只想在发生错误时看到,这样我们就可以看到函数尝试除以哪两个数字。
defdivide(a,b):logger.debug(f"Dividing [{a}] by [{b}]")returna/bforvaluein[1,2,3,4,5,6,7,8,9,'not a number',0]:try:logger.debug(f"start dividing..")res=divide(a=10,b=value)exceptExceptionase:logger.error(f"❌An exception occurred:{e}")前几个值(1到9)成功进行了除法运算,在这些情况下通常不需要调试日志。
然而,对于像not a number和0这样的错误输入,捕获debug日志会提供有价值的上下文。我们的。我们如何回到过去检索日志?
用一行代码将你的 Python 函数转换为装饰器
解决方案概述
要创建面包屑,我们将配置我们的日志记录器以使用两个处理器:
StreamHandler:仅显示
INFO级别及以上的消息MemoryHandler:临时存储
DEBUG消息,并在发生错误时将它们传递给StreamHandler
此设置不会显示DEBUG日志,因为 StreamHandler 被设置为INFO。我们可以临时将DEBUG消息存储在 MemoryHandler 中,当我们检测到错误时,将消息刷新到 StreamHandler。然后,它将显示存储的DEBUG消息。
配置日志记录器
下面是如何使用StreamHandler进行常规输出和MemoryHandler进行缓冲DEBUG日志来配置记录器的示例:
importloggingfromlogging.handlersimportMemoryHandler# Create logger and formatter for a structured log messagelogger=logging.getLogger("my_logger")formatter=logging.Formatter(fmt="%(levelname)-7s ⏱️%(asctime)s 📍 %(funcName)12s:%(lineno)-2s 💌 %(message)s",datefmt="%H:%M:%S")# Configure stream handlerstream_handler=logging.StreamHandler()stream_handler.setLevel(logging.INFO)# Only INFO and above will be displayedstream_handler.setFormatter(formatter)logger.addHandler(stream_handler)# Configure memory handlermemory_handler=MemoryHandler(capacity=100,target=stream_handler,flushLevel=logging.ERROR)memory_handler.setFormatter(formatter)logger.addHandler(memory_handler)在上述设置中,MemoryHandler可以缓冲最多 100 条日志条目。如果发生错误,我们可以从MemoryHandler刷新日志到StreamHandler,从而得到完整的线索路径。
如何仅用 77MB 使用 Python Bloom 过滤器存储和查询 1 亿项数据
在代码中使用面包屑记录器
下面是配置好的新日志记录器的演示。注意finally块,我们在每次尝试后清除缓冲区,以保持针对每个操作的特定调试日志。
defdivide(a,b):logger.debug(f"Dividing [{a}] by [{b}]")returna/bforvaluein[1,2,3,4,5,6,7,8,9,'not a number',0]:try:logger.debug("Start dividing..")res=divide(a=10,b=value)exceptExceptionase:logger.error(f"❌ An exception occurred:{e}")finally:memory_handler.buffer.clear()# Clear memory after each pass直到not a number,我们不会看到任何日志,因为所有前面的值执行时都没有错误。然而,当我们处理not a number时,divide函数会抛出异常,因为我们不能将整数除以字符串。
我们最终进入except块并记录一个ERROR。我们已将MemoryHandler配置为在遇到ERROR时将所有缓冲日志刷新到StreamHandler,这正是本例中将要发生的事情。
最后,我们在finally块中清除缓冲区,以便在开始处理not a number时删除9的DEBUG日志。
样本输出
当我们运行最终代码时,只有错误情况的日志会显示调试信息作为线索。以下是完整输出的样子:
# Logs corresponding to 'not a number'ERROR ⏱️17:07:03📍 main:44💌 ❌ An exception occurred:unsupported operandtype(s)for/:'int'and'str'DEBUG ⏱️17:07:03📍 main:41💌 Start dividing..DEBUG ⏱️17:07:03📍 divide:32💌 Dividing[10]by[nota number]# Logs corresponding to 0ERROR ⏱️17:07:03📍 main:44💌 ❌ An exception occurred:division by zero DEBUG ⏱️17:07:03📍 main:41💌 Start dividing..DEBUG ⏱️17:07:03📍 divide:32💌 Dividing[10]by[0]在这些示例中,MemoryHandler仅在遇到错误时捕获和刷新调试日志,提供了一个清晰的线索路径,提供了关于错误的有价值信息。
用两行代码应用 Python 多进程
结论
通过这种设置,我们通过缓冲调试日志并仅在遇到错误时显示它们,实现了更精简的日志记录过程。这种面包屑式的方法对于性能和日志量管理至关重要的应用程序来说非常理想。MemoryHandler为我们提供了两全其美的解决方案:当我们需要时提供详细的跟踪,当我们不需要时最小化日志量。
希望这篇文章和我希望的一样清晰,如果不是这样,请告诉我我可以做些什么来进一步澄清。同时,查看我关于各种编程相关主题的**其他文章**。
开心编码!
— Mike
P.s: 喜欢我在做的事情吗?关注我!