Python 多进程日志记录

转载 2015年11月17日 15:50:56

刚开始用 Python 做 web 开发的时候我就想一个问题,如果 Python 应用需要自己记录一些比 accesslog 更详细的日志(使用 Python 的 logging module),又有多个进程,怎么办最好呢?多个进程往同一个日志文件写入会不会出问题?

最近有个在 Apache 里用 mod_wsgi 运行的程序,设置了4个 process. 最初没有设置日志的 rotation,看起来一切正常。有一天设定了每天 midnight rotate(换成 TimedRotatingFileHandler), 第二天就出问题了,前一天的日志完全丢失,当天日志分散在前一天和当天的两个文件里,并且两个文件都在增长。比如今天是2011-08-14,现在去观察就会发现昨天的 customlog-20110813 和今天的 customlog 两个文件都在被写入。

看了一下 TimedRotatingFileHandler 的 doRollover 方法:

    t = self.rolloverAt - self.interval
    if self.utc:
        timeTuple = time.gmtime(t)
    else:
        timeTuple = time.localtime(t)
    dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
    if os.path.exists(dfn):
        os.remove(dfn)
    os.rename(self.baseFilename, dfn)
    ...

这就了然了。每个进程在过了 rotate 时间点之后写第一条日志的时候,都会执行这个 doRollover,先看有没有 customlog-20110813 存在,存在的话删掉,把 customlog 改名为 customlog-20110813(注意这一步,文件名只是文件的一个属性,如果有进程已经打开该文件正在写入,并不会受影响,除非文件被删除),然后往新的 customlog 里写入。等四个进程都执行完这个方法的时候,就是一团糟了,不仅昨天的日志完全被删除,今天的日志也会有一小部分被删掉了。

多进程 (Multiprocessing) vs. 多线程 (multithreading)

假如只有一个进程,就不用操心这些了。我这个 qingbo.net 一直都是设置1个进程。于是我在 Stack Overflow 上问,如果 multithreading 就够了,为啥还要 multiprocessing 呢?回答者 Graham 好像是 mod_wsgi 的作者,从他的回答看,对于 Python 来说,由于 GIL 的限制,多进程才能利用多 CPU 或 core 的架构。

好吧,想想有什么办法可以解决这个问题。

Write separate files

取得当前进程 ID (os.getpid()),然后把这个 pid 作为日志文件名的一部分,这样各个进程的日志操作就完全不会互相影响了。

缺点是当服务器重启的时候,新的进程会有新的 pid,于是旧的文件不会被改成带有日期标识的名字,看起来不是那么干净。不过这个问题通过写一个简单的 shell 脚本即可修复,可以放在 crontab 里每天凌晨检查前一天的日志。

结合 WatchedFileHandler 与 TimedRotatingFileHandler?

以下是 WatchedFileHandler 的 emit 方法:

def emit(self, record):
    """
    Emit a record.

    First check if the underlying file has changed, and if it
    has, close the old stream and reopen the file to get the
    current stream.
    """
    if not os.path.exists(self.baseFilename):
        stat = None
        changed = 1
    else:
        stat = os.stat(self.baseFilename)
        changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
    if changed and self.stream is not None:
        self.stream.flush()
        self.stream.close()
        self.stream = self._open()
        if stat is None:
            stat = os.stat(self.baseFilename)
        self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
    logging.FileHandler.emit(self, record)

每写一条日志之前它先判断日志文件是不是被改名了,如果是的话就重新创建一个。如果 TimedRotatingFileHandler 在 doRollover 之前做这么一个检查,不就解决问题了吗?不过实际上这又牵扯到进程间同步的问题,还挺复杂的。

WatchedFileHandler 结合 cron job

一个取巧的办法是使用 WatchedFileHandler,并在 crontab 里每天零点添加一个任务,把日志文件改名。于是所有的进程在之后写第一条日志的时候会发现这一点,开始往新的文件里写入。

我没有尝试这个办法所以不知到是不是有问题。但是它的前提是没一个写入操作是一个 atom operation,否则两条日志有可能混杂在一起,就坏了。我不太了解,不过这里有个讨论,看起来每条日志不超过 4K 的话是安全的。该文还展示了另一种办法——

将日志发给一个独立的进程

Python 的 logging module 还提供了一个 SocketHandler,用来把日志通过网络发送出去。文档里有一个非常基本的服务器端的实现。有人还基于 Twisted 写了一个更好的 logging server.

但是这样我们除了需要维护 web server,又要维护 logging server. 万一 logging server 挂了怎么办?我有点担心。

把日志发给 web server

其实也是发给一个独立进程了。mod_wsgi 提供了一种途径,可以把 debug 信息写到 Apache 的 errorlog 里。不过把应用日志跟服务器 errorlog 混在一起,这显然不是什么好办法。

比起多进程的解决方案,我更倾向于使用单线程省去这些烦恼。一方面是我的服务器比较弱,只有两个 core :) 正如 Graham 所说,除了 python code,还有许多处理比如接受、分发请求,发送响应回客户端,处理静态文件请求等都是在 Apache 的 C code里执行的,所以不会让一个 core 忙不过来另一个却闲着。我倒没有实际经验看看单个 Python 应用进程在4核或8核机器上面对大并发量的情况。

单进程的另一个好处,可以不用 memcached 之类的东西,自己直接在进程的内存里方便地作缓存。如果有多个进程这么搞,那就太浪费内存了。

相关文章推荐

Python多进程记录日志

用gevent(或封装了gevent的gunicore)启动python进程,会出现多个独立进程同时写一个日志文件, 可以观察到有日志部分丢失:一个进程日志没写完,另一个进程把日志覆盖在同一行的后面...

python进程写文件与多进程记录日志

假设有个进程,一直在写一个名字叫做1的文件,然后我们在它运行的过程中将文件改名了,会发生什么事情呢? 写个程序试试: 然后运行它,此时可以看到: 因为进程得到了文件的句柄,所以就算这个文件改名...

python的日志logging模块性能以及多进程

写在前面:     日志是记录操作的一种好方式。但是日志,基本都是基于文件的,也就是要写到磁盘上的。这时候,磁盘将会成为一个性能瓶颈。对于普通的服务器硬盘(机械磁盘,非固态硬盘),python日志的...

Python 中 logging 日志模块在多进程环境下的使用

使用 Python 来写后台任务时,时常需要使用输出日志来记录程序运行的状态,并在发生错误时将错误的详细信息保存下来,以别调试和分析。Python 的 logging 模块就是这种情况下的好帮手。 ...

51.[Python]使用multiprocessing进行多进程编程

介绍多进程的基础知识,及使用multiprocessing进行多进程编程的方法,给出了代码示例及github项目地址。...

python logging 重复写日志问题

python logging 重复写日志问题用Python的logging模块记录日志时,遇到了重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次。。。很头疼,这样记日志可不行。...

多进程、多线程并发写同一log文件问题

两个独立进程各自打开同一个文件 如果用O_APPEND标志打开了一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件偏移量...

Python多进程写入同一文件

最近用python的正则表达式处理了一些文本数据,需要把结果写到文件里面,但是由于文件比较大,所以运行起来花费的时间很长。但是打开任务管理器发现CPU只占用了25%,上网找了一下原因发现是由于一个叫G...

使用python logging处理多机多进程写同一个日志文件

MemoryHandler的性能问题: 如果target是StreamHandler的子类 上是有严重的IO性能问题 是一个for调用handler, handler中的处理侧是 io.w...

Python3给线程关键部分加锁

要在多线程程序中安全使用可变对象,需要使用 threading 库中的 Lock 对象。import threadingclass SharedCounter: ''' 一个可以由多个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Python 多进程日志记录
举报原因:
原因补充:

(最多只允许输入30个字)