[英]Does python logging support multiprocessing?
有人告诉我日志不能在多处理中使用。 您必须进行并发控制,以防多处理弄乱日志。
但是我做了一些测试,在多处理中使用登录似乎没有问题
import time
import logging
from multiprocessing import Process, current_process, pool
# setup log
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='/tmp/test.log',
filemode='w')
def func(the_time, logger):
proc = current_process()
while True:
if time.time() >= the_time:
logger.info('proc name %s id %s' % (proc.name, proc.pid))
return
if __name__ == '__main__':
the_time = time.time() + 5
for x in xrange(1, 10):
proc = Process(target=func, name=x, args=(the_time, logger))
proc.start()
从代码中可以看出。
我故意让子进程在同一时刻(启动后5s)写入日志以增加冲突的机会。 但根本没有冲突。
所以我的问题是我们可以在多处理中使用日志记录吗? 为什么这么多帖子说我们不能?
正如 Matino 正确解释的那样:登录多处理设置是不安全的,因为多个进程(对现有的其他进程一无所知)正在写入同一个文件,可能会相互干扰。
现在发生的情况是每个进程都持有一个打开的文件句柄,并在该文件中执行“追加写入”。 问题是在什么情况下追加写入是“原子的”(也就是说,不能被例如另一个进程写入同一个文件并混合他的输出)。 这个问题适用于每种编程语言,因为最终它们会对内核进行系统调用。 这个答案回答了在什么情况下共享日志文件是可以的。
它归结为检查您的管道缓冲区大小,在 linux 上定义在/usr/include/linux/limits.h
并且是 4096 字节。 对于其他操作系统,您可以在这里找到一个很好的列表。
这意味着:如果您的日志行少于 4'096 字节(如果在 Linux 上),那么附加是安全的,如果磁盘是直接连接的(即中间没有网络)。 但有关更多详细信息,请查看我的答案中的第一个链接。 要对此进行测试,您可以使用不同的长度执行logger.info('proc name %s id %s %s' % (proc.name, proc.pid, str(proc.name)*5000))
。 例如,对于 5000,我已经在/tmp/test.log
混淆了日志行。
在这个问题中已经有很多解决方案,所以我不会在这里添加我自己的解决方案。
更新:烧瓶和多处理
如果由 uwsgi 或 nginx 托管,像flask这样的Web框架将在多个worker中运行。 在这种情况下,多个进程可能会写入一个日志文件。 会不会有问题?
flask 中的错误处理是通过 stdout/stderr 完成的,然后由网络服务器(uwsgi、nginx 等)执行,需要注意以正确的方式写入日志(参见例如 [this flask+nginx example])( http://flaviusim.com/blog/Deploying-Flask-with-nginx-uWSGI-and-Supervisor/ ),可能还会添加进程信息,以便您可以将错误行与进程相关联。 来自烧瓶文档:
默认情况下,从 Flask 0.11 开始,错误会自动记录到您的网络服务器日志中。 然而警告不是。
因此,如果您使用warn
并且消息超过管道缓冲区大小,您仍然会遇到混合日志文件的问题。
从多个进程写入单个文件是不安全的。
尽管日志记录是线程安全的,并且支持从单个进程中的多个线程记录到单个文件,但不支持从多个进程记录到单个文件,因为没有标准方法可以跨多个序列化对单个文件的访问Python 中的进程。
一种可能的解决方案是让每个进程写入自己的文件。 您可以通过编写自己的处理程序将进程 pid 添加到文件末尾来实现这一点:
import logging.handlers
import os
class PIDFileHandler(logging.handlers.WatchedFileHandler):
def __init__(self, filename, mode='a', encoding=None, delay=0):
filename = self._append_pid_to_filename(filename)
super(PIDFileHandler, self).__init__(filename, mode, encoding, delay)
def _append_pid_to_filename(self, filename):
pid = os.getpid()
path, extension = os.path.splitext(filename)
return '{0}-{1}{2}'.format(path, pid, extension)
然后你只需要调用addHandler
:
logger = logging.getLogger('foo')
fh = PIDFileHandler('bar.log')
logger.addHandler(fh)
使用队列正确处理并发,同时通过管道将所有内容提供给父进程,从而从错误中恢复。
from logging.handlers import RotatingFileHandler
import multiprocessing, threading, logging, sys, traceback
class MultiProcessingLog(logging.Handler):
def __init__(self, name, mode, maxsize, rotate):
logging.Handler.__init__(self)
self._handler = RotatingFileHandler(name, mode, maxsize, rotate)
self.queue = multiprocessing.Queue(-1)
t = threading.Thread(target=self.receive)
t.daemon = True
t.start()
def setFormatter(self, fmt):
logging.Handler.setFormatter(self, fmt)
self._handler.setFormatter(fmt)
def receive(self):
while True:
try:
record = self.queue.get()
self._handler.emit(record)
except (KeyboardInterrupt, SystemExit):
raise
except EOFError:
break
except:
traceback.print_exc(file=sys.stderr)
def send(self, s):
self.queue.put_nowait(s)
def _format_record(self, record):
# ensure that exc_info and args
# have been stringified. Removes any chance of
# unpickleable things inside and possibly reduces
# message size sent over the pipe
if record.args:
record.msg = record.msg % record.args
record.args = None
if record.exc_info:
dummy = self.format(record)
record.exc_info = None
return record
def emit(self, record):
try:
s = self._format_record(record)
self.send(s)
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
def close(self):
self._handler.close()
logging.Handler.close(self)
处理程序完成父进程的所有文件写入工作,并仅使用一个线程来接收从子进程传递的消息
QueueHandler
在 Python 3.2+ 中是原生的,并且可以安全地处理多处理日志记录。
Python 文档有两个完整的示例: 从多个进程记录到单个文件
对于那些使用 Python < 3.2 的用户,只需将QueueHandler
复制到您自己的代码中: https: //gist.github.com/v
每个进程(包括父进程)将其日志记录放在Queue
上,然后一个listener
器线程或进程(为每个进程提供一个示例)拾取这些并将它们全部写入文件 - 没有损坏或乱码的风险。
注意:这个问题基本上与在 Python 中使用多处理时如何记录? 所以我从那个问题中复制了我的答案,因为我很确定这是目前最好的解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.