Python不会释放文件句柄到日志文件。

65

我有一个应用程序需要运行多个模拟操作。我想建立一个日志机制,将所有的日志记录在general.log中,并将每个模拟操作的日志单独存储到run00001.log等文件中。为此,我定义了一个Run类,在__init__()中添加一个新的文件处理器以处理运行日志。

问题是运行操作的日志文件从未被释放,因此经过一定次数的运行后,可用句柄会耗尽并导致运行崩溃。

我设置了一些例程来测试这个问题,如下所示:

主例程

import Model
try:
    myrun = Model.Run('20130315150340_run_49295')
    ha = raw_input('enter')
    myrun.log.info("some info")
except:
    traceback.print_exc(file=sys.stdout)

ha = raw_input('enter3')

类 Run 在模块 Model 中定义如下

import logging
class Run(object):

    """ Implements the functionality of a single run. """
    def __init__(self, runid):
        self.logdir="."
        self.runid          = runid
        self.logFile        = os.path.join(self.logdir , self.runid + '.log')
        self.log            = logging.getLogger('Run'+self.runid)
        myformatter         = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
        myhandler      = logging.FileHandler(self.logFile)
        myhandler.setLevel(logging.INFO)
        myhandler.setFormatter(myformatter)
        self.log.addHandler(myhandler) 

然后我使用程序进程管理器来跟踪文件处理程序。我看到运行日志出现了,但从未消失过。

有没有办法可以强制它消失?


1
为什么不在运行完成后再次删除处理程序?可能你可以钩入那个事件? - Martijn Pieters
有关如何做到这一点的任何建议?我已经尝试在__del__()中指定self.log.removeHandler(myhandler),甚至通过显式调用析构函数(myrun.del())来调用它。我还尝试了在__exit__()中指定并使用with语句,就像使用open打开文件句柄时建议的那样。但是到目前为止都没有成功。 - Bart P.
3个回答

115

你需要在文件处理程序上调用.close()

当你的Run类完成时,请调用:

handlers = self.log.handlers[:]
for handler in handlers:
    self.log.removeHandler(handler)
    handler.close()

每当有新的日志消息到达时,文件处理程序会自动重新打开配置的文件名,因此调用 handler.close() 有时可能看起来是徒劳无功的。从记录器中删除处理程序将停止将来的日志记录发送到它;在上面的代码中,我们首先这样做,以避免来自另一个线程重新打开处理程序的不及时日志消息。

这里的另一个答案建议您使用 logging.shutdown()。但是,logging.shutdown() 所做的只是调用 handler.flush()handler.close(),我不建议使用它。它会使logging模块处于一种状态,您无法再次可靠地使用 logging.shutdown()


我正在使用交互式Python环境(Spyder)。显然,Spyder在内部使用日志记录。因此,logging.shutdown()没有产生预期的效果。同一程序的下一次执行会使日志记录翻倍,第三次执行会使它们增加三倍,以此类推。在这种环境中,关闭处理程序显然不会被shutdown()移除。此外,我没有通过发出显式的shutdown()调用来干扰Spyder。令人困惑。Martijn的代码逐个显式关闭和删除处理程序,在Spyder环境中确实奏效。 - Richard Elkins

41

您还可以完全关闭日志记录。 在这种情况下,文件句柄将被释放:

logging.shutdown()

它关闭了所有配置的日志处理程序的打开句柄。

我需要它来在单元测试结束后能够删除日志文件,我可以在调用 logging.shutdown() 方法后立即删除它。


4
然而,在此调用之后,您不能使用记录系统 - 这只是一个提醒。 - Shital Shah
3
这并不完全关闭日志记录。它在每个处理程序上调用flush()close()。虽然.close()会从全局模块状态中移除处理程序,并且在shutdown()调用后不会再次关闭,但是文件处理程序通常在下一条日志消息时重新打开其文件句柄。由于它们没有从其父记录器中删除,这仅意味着它们再次打开文件以写入下一个传入的日志记录,然后无法由下一个shutdown调用关闭。将使用logging.shutdown()留给解释器。 - Martijn Pieters
@ShitalShah:至少在文件处理程序及其子类(流处理程序,例如控制台,是FileHandler的父类,不会这样做)方面,您可以这样做。 logging.shutdown()只是刷新和关闭已注册的处理程序,而Handler.close()在关闭时从全局注册表中取消注册处理程序。就是这样。处理程序仍然连接到记录器,日志记录仍然会将记录传递给处理程序,因此如果它们有要发出的地方,它们仍然可以发出这些记录。 - Martijn Pieters

0

可能我们只需要.close()关闭FileHandler而不关闭其他的,因此被接受的答案可以稍作修改,如下:

for handler in self.log.handlers:
    if isinstance(handler, logging.FileHandler):
        handler.close()

此外,对于仅配置了logging.basicConfig()的简单情况,可以通过调用logging.getLogger().handlers来检索处理程序。

为什么你不想关闭其他处理程序呢?如果你正在使用socket、syslog或HTTP处理程序,你可能也想要关闭它们。总之:这取决于情况! - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接