web-dev-qa-db-ja.com

pythonロギングはマルチプロセッシングをサポートしていますか?

マルチプロセッシングではロギングを使用できないと言われました。マルチプロセッシングがログを混乱させる場合には、同時実行制御を行う必要があります。

しかし、私はいくつかのテストを行いましたが、マルチプロセッシングでロギングを使用しても問題はないようです

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()

コードからわかるように。

競合の可能性を高めるために、同じ時点(開始後5秒)でサブプロセスに意図的にログを書き込ませています。しかし、競合はまったくありません。

だから私の質問は、マルチプロセッシングでロギングを使用できますか?なぜそんなに多くの投稿が私たちができないと言っているのですか?

23
Kramer Li

Matinoが正しく説明しているように、複数のプロセス(既存のプロセスについては何も知らない)が同じファイルに書き込み、潜在的に相互に干渉するため、マルチプロセッシングセットアップでのログインは安全ではありません。

ここで何が起こるかは、すべてのプロセスが開いているファイルハンドルを保持し、そのファイルに「書き込みを追加」することです。問題は、どのような状況で追加書き込みが「アトミック」であるか(つまり、別のプロセスが同じファイルに書き込み、出力を混在させることによって中断できないこと)です。この問題はすべてのプログラミング言語に当てはまり、最終的にはカーネルに対してシステムコールを実行します。 この回答 共有ログファイルが問題ない状況での回答。

Linuxでは、_/usr/include/linux/limits.h_で定義されている4096バイトのパイプバッファーサイズを確認することになります。他のOSについては here が良いリストです。

つまり、ログ行が4'096バイト未満の場合(Linuxの場合)、ディスクが直接接続されている(つまり、間にネットワークがない)場合、追加は安全です。しかし、詳細については、私の回答の最初のリンクを確認してください。これをテストするには、異なる長さでlogger.info('proc name %s id %s %s' % (proc.name, proc.pid, str(proc.name)*5000))を実行できます。たとえば5000では、_/tmp/test.log_のログ行が既に混同されています。

この質問 では、これに対する解決策がすでにかなりあるので、ここに自分の解決策を追加しません。

更新:Flask and multiprocessing

flaskなどのWebフレームワークは、uwsgiまたはnginxでホストされている場合、複数のワーカーで実行されます。その場合、複数のプロセスが1つのログファイルに書き込む可能性があります。

flaskでのエラー処理は、ログが正しい方法で書き込まれるように注意する必要があるWebサーバー(uwsgi、nginxなど)によって取得されるstdout/stderrを介して行われます(たとえば、 [このフラスコ+ nginxの例])( http://flaviusim.com/blog/Deploying-Flask-with-nginx-uWSGI-and-Supervisor/ )、おそらくプロセス情報を追加して、エラー行をプロセスに関連付けます flasks doc から:

Flask 0.11の時点で、エラーはウェブサーバーのログに自動的に記録されます。ただし、警告は記録されません。

したがって、warnを使用し、メッセージがパイプバッファーサイズを超えると、ログファイルが混在するというこの問題が引き続き発生します。

17
hansaplast

複数のプロセスから単一のファイルに書き込むことは安全ではありません。

https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes

ロギングはスレッドセーフであり、単一プロセスの複数のスレッドから単一ファイルへのロギングはサポートされていますが、複数のプロセスから単一ファイルへのアクセスをシリアル化する標準的な方法がないため、複数プロセスからの単一ファイルへのロギングはサポートされていませんPythonでのプロセス。

1つの可能な解決策は、各プロセスが独自のファイルに書き込むようにすることです。これを実現するには、プロセス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)
20
matino

キューを使用して、パイプを介してすべてを親プロセスにフィードすることにより、エラーから同時に回復する並行処理を正しく処理します。

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)

ハンドラーは親プロセスからのすべてのファイル書き込みを行い、1つのスレッドのみを使用して子プロセスから渡されたメッセージを受信します

1
Prakhar Agarwal