Python日志记录导致延迟?

Python日志记录导致延迟?,python,performance,logging,latency,Python,Performance,Logging,Latency,我正在使用Python3+瓶/UWSGI开发一个实时restapi。我在代码中遇到了延迟,有时是100毫秒,这在我的应用程序中很重要 使用logging模块,我试图识别代码中速度较慢的部分,打印各个代码块运行的时间。我知道这是一种非常糟糕的分析代码的方法,但有时它能够很好地完成这项工作 尽管我识别出了一些慢的部分,但我仍然遗漏了一些东西——单个部分似乎需要10毫秒,但通常情况下,它们作为一个整体需要100毫秒。经过一些越来越疯狂的实验,几乎让我发疯,我得出以下结论: t = round(100*

我正在使用Python3+瓶/UWSGI开发一个实时restapi。我在代码中遇到了延迟,有时是100毫秒,这在我的应用程序中很重要

使用
logging
模块,我试图识别代码中速度较慢的部分,打印各个代码块运行的时间。我知道这是一种非常糟糕的分析代码的方法,但有时它能够很好地完成这项工作

尽管我识别出了一些慢的部分,但我仍然遗漏了一些东西——单个部分似乎需要10毫秒,但通常情况下,它们作为一个整体需要100毫秒。经过一些越来越疯狂的实验,几乎让我发疯,我得出以下结论:

t = round(100*time.time())
logging.info('[%s] Foo' % t)
logging.info('[%s] Bar' % t)
令人惊讶的是,它给出了:

2014-07-16 23:21:23,531  [140554568353] Foo
2014-07-16 23:21:24,312  [140554568353] Bar
尽管这似乎很难相信,但是有两个后续的
logging.info()
调用,由于某些原因,它们之间的间隔几乎为800毫秒。有人能告诉我发生了什么事吗?值得注意的是,如果有多个
info()
调用,则延迟在整个API方法中只出现一次,最常见的是在其最开始时(在第一次调用之后)。我唯一的假设是磁盘延迟,有几个(但不是那么多!)工作进程同时运行,我使用的是旋转磁盘,没有SSD。但我认为有缓冲区,操作系统会为我异步刷新磁盘。我的假设错了吗?我应该完全避免登录以避免延迟吗

编辑

根据Vinay Sajip的建议,我切换到以下初始化代码:

log_que = queue.Queue(-1)
queue_handler = logging.handlers.QueueHandler(log_que)
log_handler = logging.StreamHandler()
queue_listener = logging.handlers.QueueListener(log_que, log_handler)
queue_listener.start()
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s  %(message)s", handlers=[queue_handler])

它似乎工作正常(如果我注释
queue\u listener.start()
,则没有输出),但仍然会出现相同的延迟。我不知道怎么可能,呼叫应该是非阻塞的。我还将
gc.collect()
放在每个请求的末尾,以确保问题不是由垃圾收集器引起的,不会产生任何影响。我还试着一整天都关掉日志记录。延迟消失了,所以我认为它们的来源必须在
日志记录
模块中…

这可能取决于日志记录处理程序。我的经验是,例如,作为日志处理程序使用的PostgreSQL在速度上是一个非常糟糕的选择。FileHandler可能会给您带来非常好的结果,但如果您的系统非常I/O繁忙,那么即使是简单的文件写入也可能会很慢。我建议使用一些异步处理程序,例如,通过UDP将日志发送到专用进程。

尝试使用Logbook提供的异步日志 正如hasan所建议的,异步日志处理程序是一种可行的方法


最近,我尝试使用
日志
,可以说,它将为您提供所需的一切-以及您可以使用异步处理程序(以及相应的
QueueListener
,在Python 3.2中添加,并在中描述)并在单独的线程或进程中对日志事件进行I/O处理。

首先,从逐出队列(循环缓冲区)开始。。。。这确保队列处理程序不能使用所有可用的RAM

class EvictQueue(Queue):
    def __init__(self, maxsize):
        self.discarded = 0
        super().__init__(maxsize)

    def put(self, item, block=False, timeout=None):
        while True:
            try:
                super().put(item, block=False)
            except queue.Full:
                try:
                    self.get_nowait()
                    self.discarded += 1
                except queue.Empty:
                    pass
然后替换根目录中的所有处理程序。。。在正常配置之后,无论它们是什么

def speed_up_logs(): 
    rootLogger = logging.getLogger()     
    log_que = EvictQueue(1000)
    queue_handler = logging.handlers.QueueHandler(log_que)
    queue_listener = logging.handlers.QueueListener(log_que, *rootLogger.handlers)
    queue_listener.start()
    rootLogger.handlers = [queue_handler]
影响:

  • 日志记录将非常快

  • 如果您的登录速度快于写入驱动器的速度,则旧的未写入项将被自动丢弃

  • 通过记录每分钟左右丢弃的条目数的单个条目(将丢弃的条目替换为零),可以更好地增强这一点


我怀疑是系统问题,而不是代码问题。例如,如果你在ubuntu上,没有定义交换文件,或者如果你在windows上,硬盘上的容量小于20GB,你会遇到响应丢失的问题。如果你能将它简化为一个小型的独立脚本,显示问题,请发布。否则,很难看到多线程环境中发生了什么。您是否可以确保在系统中的任何位置(例如,在第三方库中)都没有定义同步处理程序?这可能会影响延迟。库不应该添加除
NullHandler
以外的处理程序,但是开发人员并不总是按照他们应该的方式进行操作。另外请注意,
basicConfig()
format=
参数只适用于在该调用中添加的处理程序,即在这种情况下,
QueueHandler
。它不使用格式化程序,
StreamHandler
将不会设置格式化程序,因为它只传递给
QueueListener
。您需要显式设置格式化程序。@VinaySajip您完全正确,这是由UWSGI“神奇的”进程分叉造成的。当我打印当前使用的处理程序时,
QueueHandler
消失,默认的
StreamHandler
再次出现。我现在正尝试在进程分叉后使用延迟初始化创建处理程序。亲爱的downvoter-给我一个机会学习,如何改进我的答案。我不是downvoter(实际上我只是给了你+1,让你指向日志),但我仍然必须说Vinay Salip的答案,关于我的问题,更直接地解决问题。但我可能会考虑将来迁移到日志。[捷克语:Přesto díky!]@Tregoreg感谢您的反馈。我并不是说我的答案是最好的,但我希望反对票会出于某种原因警告其他人“不要听从这个建议”。就我个人而言,
Logbook
帮助了我,因为我不用自己编写TCP或UDP协议就可以实现异步分布式日志系统。我知道,它是有效的。老实说,4年后,我们根据你最初的建议在日志上到处运行,只是没能写在这里:)它工作得很好,问题再也没有出现过,所以你的答案是正确的!看来这正好解决了我的问题!我刚刚将其投入生产,日志记录延迟似乎消失了。此外,我只需要做很少的编程来修复