2017-12-25 15 views
18

マルチプロセッシングでロギングを使用できないと言われました。マルチプロセッシングがログを混乱させる場合に備えて、並行性制御を行う必要があります。ログを使用しても問題はあなたがコードから見ることができるように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秒)にログを書き込ませます。しかし、全く矛盾はありません。

私はマルチプロセッシングでのログを使用することができますか? なぜ多くの記事は私たちができないと言っているのですか?

答えて

8

:ログイン

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を呼び出す必要があります:あなたは、ファイルの最後にプロセスpidを追加し、独自のハンドラを記述することでこれを達成することができますマルチプロセッシングのセットアップは安全ではありません。複数のプロセス(現在存在する他のプロセスについて知っている人)が同じファイルに書き込んでいるため、相互に潜在的に介入する可能性があります。

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

これは、linuxのパイプバッファサイズを調べることになります。これは、で定義され、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に混在しています。

this questionにはすでにこれに対応した解決策がいくつかありますので、ここでは私自身の解決方法を追加しません。

アップデート:uwsgiまたはnginxのが主催している場合フラスコ、フラスコのような

Webフレームワークをマルチプロセッシングは、複数の作業者で実行されます。その場合には、複数のプロセスは、世話をする必要がフラスコ内のエラー処理は、次に、ウェブサーバ(uwsgi、nginxの、等)によってcoughtれるSTDOUT/stderrを介して行われる

1つのログファイルに書き込むことができること。ログは(あなたがプロセスにエラー行を関連付けることができますので、おそらくまた、プロセス情報を追加flasks docから、例えばthis flask+nginx exampleを参照してください正しい方法で書かれています:

デフォルトでフラスコ0.11のように、エラーは自動的にあなたのウェブサーバのログに記録されます。しかし、警告はありません。

warnを使用し、メッセージがパイプバッファサイズを超えている場合は、このような混在ログファイルの問題が残ります。

+0

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

+0

@KramerLi:私は私の答えの新しいセクションの下であなたの質問に答えました – hansaplast

13

複数のプロセスから1つのファイルに書き込むことは安全ではありません。ロギングはスレッドセーフであり、単一のプロセスで 複数のスレッドから単一のファイルへのロギングがサポートされていますがhttps://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes

によると、複数のプロセスから単一 ファイルへのロギングは、サポートされていません

なぜなら、Pythonの複数の プロセスにまたがって1つのファイルへのアクセスをシリアライズするための標準的な方法は ではないからです。

可能な解決策の1つは、各プロセスが独自のファイルに書き込むことです。マティーノを正しく説明したように

logger = logging.getLogger('foo') 
fh = PIDFileHandler('bar.log') 
logger.addHandler(fh) 
+0

感謝を処理 – 22degrees

0

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

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つのスレッドを使用しますもう一つ質問が...スレッド対マルチのレッスンの

関連する問題