多进程Python日志记录

6
我有一个可能会长时间运行的程序,目前有4个进程,但可以配置更多。我已经研究了使用Python的logging记录来自多个进程的日志,并正在使用在此处讨论的SocketHandler方法。我从未遇到过单个记录器(无套接字)的任何问题,但根据我的阅读,我被告知它最终会失败并出现意外情况。据我所知,当您尝试同时写入同一文件时,其结果是未知的。我的代码基本上执行以下操作:
import logging
log = logging.getLogger(__name__)

def monitor(...):
    # Spawn child processes with os.fork()
    # os.wait() and act accordingly

def main():
    log_server_pid = os.fork()
    if log_server_pid == 0:
        # Create a LogRecordSocketServer (daemon)
        ...
        sys.exit(0)
    # Add SocketHandler to root logger
    ...
    monitor(<configuration stuff>)

if __name__ == "__main__":
    main()

我的问题是:每次os.fork()后,我需要创建一个新的log对象吗?现有的全局log对象会发生什么?
按照我现在的做法,我是否已经解决了我试图避免的问题(多个打开的文件/套接字)?这种方法会失败吗?为什么会失败(我想能够告诉未来类似实现将会失败的原因)?
此外,使用“正常”(一个log=表达式)记录到一个文件的方式会以何种方式失败?它会引发IOError/OSError吗?还是它只是没有完全将数据写入文件?
如果有人能提供答案或链接帮助我,那就太好了。谢谢。
FYI:我正在Mac OS X Lion上测试,代码最终可能在Windows机器上的CentOS 6虚拟机上运行(如果有影响的话)。无论我使用什么解决方案都不需要在Windows上工作,但应该在基于Unix的系统上工作。 更新: 这个问题已经开始偏离记录特定行为的范畴,更多地涉及到Linux在进程复制期间如何处理文件描述符。我翻阅了我的一本大学教科书,发现如果从两个进程(在fork之前)以追加模式打开一个文件,则只要写入不超过实际内核缓冲区(尽管可能需要使用行缓冲),它们都能正确地向文件中写入。这会创建2个文件表条目和1个v-node表条目。然后再打开一个文件进行fork操作是不应该起作用的,但是只要像之前一样不超过内核缓冲区就可以工作(我在以前的程序中做过)。
所以,如果您想要跨平台的多进程日志记录,可以使用套接字,并在每个fork之后创建一个新的SocketHandler,以确保安全,就像Vinay在下面建议的那样(这应该适用于所有情况)。对我来说,由于我对软件运行的操作系统有很强的控制力,我认为我将使用一个全局的log对象和一个FileHandler(默认情况下以追加模式打开,并在大多数操作系统上进行行缓冲)。open 的文档中写道,“负缓冲意味着使用系统默认值,通常对于tty设备是行缓冲,对于其他文件是完全缓冲。如果省略,则使用系统默认值。”或者我可以创建自己的日志流来确保行缓冲。只是为了明确,我接受:
# Process A
a_file.write("A\n")
a_file.write("A\n")
# Process B
a_file.write("B\n")

生成中...

A\n
B\n
A\n

只要它不产生...
AB\n
\n
A\n
Vinay(或其他人),我错了吗?请告诉我。谢谢你能提供更多的清晰度/确定性。

线程和锁对于这种事情非常有用... - John Doe
我需要单独的进程,因为子进程正在与应该尽可能“快速”的外部设备通信。 - djhoese
我认为你可能会通过运气得到预期的行为:A\nB\nA\n而不是AB\n\nA\n。然而,在我看来,从父进程继承文件句柄并在多个子进程中写入的方法并不能保证定义良好。 - Vinay Sajip
1个回答

2
我需要在os.fork()之后创建新的日志对象吗?现有的全局日志对象会发生什么?
据我所知,全局日志对象在父进程和子进程中仍然指向同一个记录器。因此您不需要创建新的记录器。但是,我认为您应该在monitor()中的fork()之后创建并添加SocketHandler,这样套接字服务器就会有四个不同的连接,分别连接到每个子进程。如果不这样做,则在monitor()中分叉出的子进程将继承其父进程的SocketHandler及其套接字句柄,但我不确定它是否会发生异常行为。行为可能取决于操作系统,您可能会在OSX上运气好。
通过我的方式进行操作,我能够避免我试图避免的问题吗(多个打开的文件/套接字)?这会失败吗?为什么会失败(我希望能够告诉未来类似实现的人如何避免失败)?
如果您按照我上面建议的最后一个fork()之后创建套接字连接,则不会预计会发生故障,但是我不确定在其他情况下行为是否定义良好。您提到了多个打开的文件,但我在您的代码片段中没有看到打开文件的引用,只是打开套接字。
此外,“正常”的(一个日志=表达式)从多个进程记录到一个文件的方法会以什么方式失败?它会引发IOError/OSError吗?还是它只是没有完全将数据写入该文件?
我认为行为未被定义良好,但我们可以预计故障模式会在文件中呈现来自不同进程的交替日志消息。
Process A writes first part of its message
Process B writes its message
Process A writes second part of its message

更新:如果您按照评论中所述的方式使用FileHandler,由于我上面描述的情况,情况将不太好:进程A和B都从文件末尾开始(因为是追加模式),但此后事情可能会失去同步,因为(例如,在多处理器上,但甚至在单处理器上也有可能),一个进程可以(抢占另一个并)在另一个进程完成之前写入共享文件句柄。

当我说多个文件时,我指的是如果我使用基本的FileHandler而不是SocketHandler。我和一个同事交谈过,他提到如果日志在Linux上以追加模式打开FileHandler,那么我应该没问题(一个日志对象,一个文件处理器,多个fork)。你有什么想法?在fork之后创建SocketHandlers是有道理的。 - djhoese
我会接受你的答案,因为在 fork 之后创建 SocketHandler,但我仍然不同意追加模式(内核应该使追加写入具有原子性)。 - djhoese

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