Pythonでマルチプロセッシングを使用しているときにログを記録するにはどうすればよいですか?


238

現在、Python 2.6 multiprocessingモジュールを使用して複数のプロセスを生成するフレームワークに中央モジュールがあります。を使用しているためmultiprocessing、モジュールレベルのマルチプロセッシング対応ログがありLOG = multiprocessing.get_logger()ます。パードキュメントあなたが文字化けでは最大のものしないように、このロガーは、プロセス共有ロックを持っているsys.stderrと同時に、それに書き込む複数のプロセスを持つこと(または任意のファイルハンドルを)。

私が今持っている問題は、フレームワーク内の他のモジュールがマルチプロセッシングに対応していないことです。私の見方では、この中央モジュールへのすべての依存関係にマルチプロセッシング対応のロギングを使用する必要があります。フレームワークのすべてのクライアントは言うまでもなく、それはフレームワーク内で迷惑です。考えていない代替案はありますか?


10
あなたがリンクしているドキュメントは、あなたの言うことの正反対を述べています、ロガーにはプロセス共有ロックがなく、物事が混同されます-私も同様に問題がありました。
Sebastian Blask、2012年

3
stdlibのドキュメントの例をご覧ください:複数のプロセスから単一のファイルへのロギング。レシピは、他のモジュールがマルチプロセッシング対応であることを必要としません。
jfs 2012

それで、ユースケースはmultiprocessing.get_logger()何ですか?ロギングを行うこれらの他の方法に基づくとmultiprocessing、ほとんど価値のないロギング機能です。
Tim Ludwinski、2014年

4
get_logger()multiprocessingモジュール自体が使用するロガーです。multiprocessing問題をデバッグする場合に役立ちます。
jfs 2015年

回答:


69

これを非侵入的に処理する唯一の方法は、次のとおりです。

  1. ログが異なるファイル記述子(ディスクまたはパイプ)に送られるように、各ワーカープロセスを生成します。理想的には、すべてのログエントリにタイムスタンプを付ける必要があります。
  2. その後、コントローラープロセスは次のいずれかを実行できます。
    • ディスクファイルを使用している場合:実行の最後にログファイルを結合し、タイムスタンプでソートします
    • パイプを使用している場合(推奨):すべてのパイプから中央のログファイルにその場でログエントリを結合します。(たとえば、定期的selectにパイプのファイル記述子から、利用可能なログエントリに対してマージソートを実行し、集中ログにフラッシュします。繰り返します。)

いいですね、それを考える前に35秒でした(私が使用すると思いましたatexit:-)。問題は、リアルタイムの読み出しができないことです。これは、マルチスレッド化とは対照的に、マルチプロセッシングの価格の一部である可能性があります。
cdleary 2009年

それは(標準エラーが生成されたプロセスにバッファリングされていない場合は特に)1を得ることができるように、近リアルタイムのようになりパイプアプローチを用い@cdleary、
vladr

1
ちなみに、ここでは大きな仮定:Windowsではありません。Windowsを使用していますか?
vladr 2009年

22
代わりに、メインプロセスでmultiprocessing.Queueとログスレッドを使用しないのはなぜですか?シンプルに思えます。
Brandon Rhodes、

1
@BrandonRhodes-私が言ったように、非侵入型。使用multiprocessing.Queueするために再配線するコードが多い場合multiprocessing.Queue、および/またはパフォーマンスが問題である
vladr

122

私は今、パイプを介してすべてを親プロセスに供給するだけの独自のログハンドラーを作成しました。私は10分間しかテストしていませんが、かなりうまくいくようです。

注:これはにハードコードされRotatingFileHandlerています。これは私自身の使用例です)。


更新:@javierは、このアプローチをPypiで利用可能なパッケージとして維持するようになりました-Pypiのmultiprocessing-logginghttps://github.com/jruere/multiprocessing-loggingの githubを参照してください


更新:実装!

これは、並行性を正しく処理するためにキューを使用し、エラーから正しく回復するようになりました。私はこれを数ヶ月間生産で使用しており、以下の現在のバージョンは問題なく動作します。

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)

4
上記のハンドラーは、親プロセスからのすべてのファイル書き込みを行い、1つのスレッドのみを使用して子プロセスから渡されたメッセージを受信します。生成された子プロセスからハンドラー自体を呼び出すと、ハンドラーが誤って使用され、RotatingFileHandlerと同じ問題が発生します。上記のコードを何年も問題なく使用してきました。
zzzeek

9
残念ながら、このアプローチはWindowsでは機能しません。docs.python.org/library/multiprocessing.htmlから16.6.2.12「Windowsでは、子プロセスは親プロセスのロガーのレベルのみを継承します。ロガーの他のカスタマイズは継承されません。」サブプロセスはハンドラーを継承せず、ピックル可能ではないため明示的に渡すことはできません。
Noah Yetter

2
multiprocessing.Queueスレッドを使用することは注目に値しput()ます。そのためputMultiProcessingLogすべてのサブプロセスを作成する前に呼び出し(つまり、ハンドラーを使用してメッセージをログに記録)しないでください。そうでなければ、スレッドは子プロセスで死んでしまいます。1つの解決策は、Queue._after_fork()各子プロセスの開始時に呼び出すか、multiprocessing.queues.SimpleQueue代わりに使用することです。これは、スレッドを含まないがブロックします。
Danqi Wang 2013

5
初期化と、仮想的な子プロセスの使用法を示す簡単な例を追加できますか?子プロセスがクラスの別のインスタンスをインスタンス化せずにキューにアクセスする方法がよくわかりません。
JesseBuesking 2014

11
@zzzeek、このソリューションは優れていますが、それを含むパッケージが見つからなかったため、というパッケージを作成しましたmultiprocessing-logging
ハビエル

30

QueueHandlerPython 3.2以降ではネイ​​ティブであり、まさにこれを行います。以前のバージョンで簡単に複製できます。

Pythonのドキュメントには2つの完全な例があります。複数のプロセスから単一のファイルへのロギング

Python <3.2を使用QueueHandlerしている場合は、https//gist.github.com/vsajip/591589から独自のコードにコピーするか、代わりにlogutilsをインポートします

(親プロセスを含む)各プロセスは、上のログを置きQueue、その後、listenerスレッドまたはプロセスは、(1例がそれぞれ設けられている)、これらのアップをピックアップし、すべてのファイルに書き込みません-破損または文字化けの危険性を。


21

以下は、Googleからここにアクセスする他の人(私のような)のためにシンプルさを重視した別のソリューションです。ロギングは簡単です!3.2以降のみ。

import multiprocessing
import logging
from logging.handlers import QueueHandler, QueueListener
import time
import random


def f(i):
    time.sleep(random.uniform(.01, .05))
    logging.info('function called with {} in worker thread.'.format(i))
    time.sleep(random.uniform(.01, .05))
    return i


def worker_init(q):
    # all records from worker processes go to qh and then into q
    qh = QueueHandler(q)
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logger.addHandler(qh)


def logger_init():
    q = multiprocessing.Queue()
    # this is the handler for all log records
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(levelname)s: %(asctime)s - %(process)s - %(message)s"))

    # ql gets records from the queue and sends them to the handler
    ql = QueueListener(q, handler)
    ql.start()

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    # add the handler to the logger so records from this process are handled
    logger.addHandler(handler)

    return ql, q


def main():
    q_listener, q = logger_init()

    logging.info('hello from main thread')
    pool = multiprocessing.Pool(4, worker_init, [q])
    for result in pool.map(f, range(10)):
        pass
    pool.close()
    pool.join()
    q_listener.stop()

if __name__ == '__main__':
    main()

2
QueueHandlerそしてQueueListenerクラスは中、同様のPython 2.7で利用可能に使用することができますlogutilsパッケージ。
Lev Levitsky、2018年

5
メインプロセスのロガーもQueueHandlerを使用する必要があります。現在のコードでは、メインプロセスがキューをバイパスしているため、メインプロセスとワーカープロセスの間で競合状態が発生する可能性があります。誰もが(QueueHandlerを介して)キューにログを記録し、QueueListenerのみがStreamHandlerにログを記録できるようにする必要があります。
Ismael EL ATIFI 2018年

また、子ごとにロガーを初期化する必要はありません。親プロセスでロガーを初期化し、各子プロセスでロガーを取得するだけです。
okwap

20

さらに別の方法としては、loggingパッケージに含まれるさまざまな非ファイルベースのロギングハンドラが考えられます

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(その他)

このようにして、安全に書き込むことができ、結果を正しく処理できるログデーモンをどこかに簡単に配置できます。(たとえば、メッセージをunpickle化し、独自のローテーションファイルハンドラーに送信する単純なソケットサーバー)。

SyslogHandlerあまりにも、あなたのためにこれの世話をするでしょう。もちろん、syslogシステムのインスタンスではなく、独自のインスタンスを使用することもできます。


13

ロギングスレッドとキュースレッドを別々に保つ他のバリアント。

"""sample code for logging in subprocesses using multiprocessing

* Little handler magic - The main process uses loggers and handlers as normal.
* Only a simple handler is needed in the subprocess that feeds the queue.
* Original logger name from subprocess is preserved when logged in main
  process.
* As in the other implementations, a thread reads the queue and calls the
  handlers. Except in this implementation, the thread is defined outside of a
  handler, which makes the logger definitions simpler.
* Works with multiple handlers.  If the logger in the main process defines
  multiple handlers, they will all be fed records generated by the
  subprocesses loggers.

tested with Python 2.5 and 2.6 on Linux and Windows

"""

import os
import sys
import time
import traceback
import multiprocessing, threading, logging, sys

DEFAULT_LEVEL = logging.DEBUG

formatter = logging.Formatter("%(levelname)s: %(asctime)s - %(name)s - %(process)s - %(message)s")

class SubProcessLogHandler(logging.Handler):
    """handler used by subprocesses

    It simply puts items on a Queue for the main process to log.

    """

    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue

    def emit(self, record):
        self.queue.put(record)

class LogQueueReader(threading.Thread):
    """thread to write subprocesses log records to main process log

    This thread reads the records written by subprocesses and writes them to
    the handlers defined in the main process's handlers.

    """

    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.daemon = True

    def run(self):
        """read from the queue and write to the log handlers

        The logging documentation says logging is thread safe, so there
        shouldn't be contention between normal logging (from the main
        process) and this thread.

        Note that we're using the name of the original logger.

        """
        # Thanks Mike for the error checking code.
        while True:
            try:
                record = self.queue.get()
                # get the logger for this record
                logger = logging.getLogger(record.name)
                logger.callHandlers(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except EOFError:
                break
            except:
                traceback.print_exc(file=sys.stderr)

class LoggingProcess(multiprocessing.Process):

    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def _setupLogger(self):
        # create the logger to use.
        logger = logging.getLogger('test.subprocess')
        # The only handler desired is the SubProcessLogHandler.  If any others
        # exist, remove them. In this case, on Unix and Linux the StreamHandler
        # will be inherited.

        for handler in logger.handlers:
            # just a check for my sanity
            assert not isinstance(handler, SubProcessLogHandler)
            logger.removeHandler(handler)
        # add the handler
        handler = SubProcessLogHandler(self.queue)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        # On Windows, the level will not be inherited.  Also, we could just
        # set the level to log everything here and filter it in the main
        # process handlers.  For now, just set it from the global default.
        logger.setLevel(DEFAULT_LEVEL)
        self.logger = logger

    def run(self):
        self._setupLogger()
        logger = self.logger
        # and here goes the logging
        p = multiprocessing.current_process()
        logger.info('hello from process %s with pid %s' % (p.name, p.pid))


if __name__ == '__main__':
    # queue used by the subprocess loggers
    queue = multiprocessing.Queue()
    # Just a normal logger
    logger = logging.getLogger('test')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(DEFAULT_LEVEL)
    logger.info('hello from the main process')
    # This thread will read from the subprocesses and write to the main log's
    # handlers.
    log_queue_reader = LogQueueReader(queue)
    log_queue_reader.start()
    # create the processes.
    for i in range(10):
        p = LoggingProcess(queue)
        p.start()
    # The way I read the multiprocessing warning about Queue, joining a
    # process before it has finished feeding the Queue can cause a deadlock.
    # Also, Queue.empty() is not realiable, so just make sure all processes
    # are finished.
    # active_children joins subprocesses when they're finished.
    while multiprocessing.active_children():
        time.sleep(.1)

キューのレコードからロガー名を取得するのが好きです。これによりfileConfig()、MainProcessで従来型を使用し、PoolWorkersでかろうじて構成されたロガーを使用できます(のみsetLevel(logging.NOTSET))。別のコメントで述べたように、私はプールを使用しているため、マルチプロセッシングの代わりにManagerからキュー(プロキシ)を取得する必要がありました。これにより、ディクショナリ内のワーカーにキューを渡すことができます(ほとんどはを使用してargsparseオブジェクトから派生していますvars())。結局、これはfork()を欠いていて@zzzeakソリューションを壊すMS Windowsのための最良のアプローチのように感じます。
mlt

@mlt Managerを使用する代わりに、マルチプロセッシングキューをinitに配置することもできると思います(stackoverflow.com/questions/25557686/…の回答を参照してください。ロックについてですが、キューでも機能すると思います)
幻想的な

@fantabolousは、MS Windowsやその他の不足しているプラ​​ットフォームでは機能しませんfork。このようにして、各プロセスは独自の独立した無用のキューを持ちます。リンクされたQ / Aの2番目のアプローチは、このようなプラットフォームでは機能しません。これは、移植性のないコードへの方法です。
mlt 2015

@mlt興味深い。私はWindowsを使用していますが、問題なく動作しているようです。最後にコメントしてから間もなくmultiprocessing.Queue、メインプロセスと共有するプロセスのプールをセットアップし、それ以来ずっとそれを使用しています。なぜそれが機能するのか理解しようとはしません。
幻想的な2015

10

現在のすべてのソリューションは、ハンドラーを使用してロギング構成に結合されすぎています。私のソリューションには、次のアーキテクチャと機能があります。

  • 必要ロギング構成を使用できます
  • ロギングはデーモンスレッドで行われます
  • コンテキストマネージャーを使用したデーモンの安全なシャットダウン
  • ロギングスレッドへの通信は、 multiprocessing.Queue
  • サブプロセスでは、logging.Logger(およびすでに定義されているインスタンス)にパッチが適用され、すべてのレコードがキューに送信されます
  • 新機能:キューに送信する前にトレースバックとメッセージをフォーマットして、酸洗いエラーを防止

使用例と出力を含むコードは、次のGistにあります。https//gist.github.com/schlamar/7003737


私が何かを見逃していない限り、これはに設定daemon_thread.daemonTrueたことがないので、これは実際にはデーモンスレッドではありません。コンテキストマネージャー内で例外が発生したときにPythonプログラムを適切に終了させるために、これを行う必要がありました。
blah238 2016年

また、のターゲットによってスローされた例外をキャッチ、ログ、および飲み込む必要がありfuncましたlogged_call。そうしないと、例外が他のログ出力で文字化けします。これが私の変更されたバージョンです:gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

ZeroMQを使用して、マルチプロセスロギングを多くのパブリッシャーと1つのサブスクライバー(リスナー)として表すことができるためをしてPUB-SUBメッセージングを実装することは確かにオプションです。

また、PyZMQのモジュール、ZMQ用Pythonバインディングは、実装PUBHandler zmq.PUBソケット上にログメッセージを発行するためのオブジェクトです。

あります、ウェブ上のソリューション簡単に複数のパブリッシングプロセスでローカルに作業するために採用することができPyZMQとPUBHandlerを使用して分散アプリケーションからの集中ロギングには、。

formatters = {
    logging.DEBUG: logging.Formatter("[%(name)s] %(message)s"),
    logging.INFO: logging.Formatter("[%(name)s] %(message)s"),
    logging.WARN: logging.Formatter("[%(name)s] %(message)s"),
    logging.ERROR: logging.Formatter("[%(name)s] %(message)s"),
    logging.CRITICAL: logging.Formatter("[%(name)s] %(message)s")
}

# This one will be used by publishing processes
class PUBLogger:
    def __init__(self, host, port=config.PUBSUB_LOGGER_PORT):
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.DEBUG)
        self.ctx = zmq.Context()
        self.pub = self.ctx.socket(zmq.PUB)
        self.pub.connect('tcp://{0}:{1}'.format(socket.gethostbyname(host), port))
        self._handler = PUBHandler(self.pub)
        self._handler.formatters = formatters
        self._logger.addHandler(self._handler)

    @property
    def logger(self):
        return self._logger

# This one will be used by listener process
class SUBLogger:
    def __init__(self, ip, output_dir="", port=config.PUBSUB_LOGGER_PORT):
        self.output_dir = output_dir
        self._logger = logging.getLogger()
        self._logger.setLevel(logging.DEBUG)

        self.ctx = zmq.Context()
        self._sub = self.ctx.socket(zmq.SUB)
        self._sub.bind('tcp://*:{1}'.format(ip, port))
        self._sub.setsockopt(zmq.SUBSCRIBE, "")

        handler = handlers.RotatingFileHandler(os.path.join(output_dir, "client_debug.log"), "w", 100 * 1024 * 1024, 10)
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter("%(asctime)s;%(levelname)s - %(message)s")
        handler.setFormatter(formatter)
        self._logger.addHandler(handler)

  @property
  def sub(self):
      return self._sub

  @property
  def logger(self):
      return self._logger

#  And that's the way we actually run things:

# Listener process will forever listen on SUB socket for incoming messages
def run_sub_logger(ip, event):
    sub_logger = SUBLogger(ip)
    while not event.is_set():
        try:
            topic, message = sub_logger.sub.recv_multipart(flags=zmq.NOBLOCK)
            log_msg = getattr(logging, topic.lower())
            log_msg(message)
        except zmq.ZMQError as zmq_error:
            if zmq_error.errno == zmq.EAGAIN:
                pass


# Publisher processes loggers should be initialized as follows:

class Publisher:
    def __init__(self, stop_event, proc_id):
        self.stop_event = stop_event
        self.proc_id = proc_id
        self._logger = pub_logger.PUBLogger('127.0.0.1').logger

     def run(self):
         self._logger.info("{0} - Sending message".format(proc_id))

def run_worker(event, proc_id):
    worker = Publisher(event, proc_id)
    worker.run()

# Starting subscriber process so we won't loose publisher's messages
sub_logger_process = Process(target=run_sub_logger,
                                 args=('127.0.0.1'), stop_event,))
sub_logger_process.start()

#Starting publisher processes
for i in range(MAX_WORKERS_PER_CLIENT):
    processes.append(Process(target=run_worker,
                                 args=(stop_event, i,)))
for p in processes:
    p.start()

6

私もzzzeekの答えが好きですが、Andreは文字化けを防ぐためにキューが必要であると正しいです。パイプで運が良かったのですが、やや予想される文字化けが見られました。それを実装することは、特にWindowsで実行しているため、私が思ったよりも難しいことがわかりました。グローバル変数などに関する追加の制限があります(WindowsでのPythonマルチプロセッシングの実装方法を参照))。

しかし、ようやく動作しました。この例はおそらく完璧ではないので、コメントや提案は大歓迎です。また、フォーマッターやルートロガー以外の設定もサポートしていません。基本的に、キューを持つ各プールプロセスでロガーを再初期化し、ロガーの他の属性を設定する必要があります。

繰り返しになりますが、コードを改善する方法についての提案は大歓迎です。私は確かにまだすべてのPythonのトリックを知りません:-)

import multiprocessing, logging, sys, re, os, StringIO, threading, time, Queue

class MultiProcessingLogHandler(logging.Handler):
    def __init__(self, handler, queue, child=False):
        logging.Handler.__init__(self)

        self._handler = handler
        self.queue = queue

        # we only want one of the loggers to be pulling from the queue.
        # If there is a way to do this without needing to be passed this
        # information, that would be great!
        if child == False:
            self.shutdown = False
            self.polltime = 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):
        #print "receive on"
        while (self.shutdown == False) or (self.queue.empty() == False):
            # so we block for a short period of time so that we can
            # check for the shutdown cases.
            try:
                record = self.queue.get(True, self.polltime)
                self._handler.emit(record)
            except Queue.Empty, e:
                pass

    def send(self, s):
        # send just puts it in the queue for the server to retrieve
        self.queue.put(s)

    def _format_record(self, record):
        ei = record.exc_info
        if ei:
            dummy = self.format(record) # just to get traceback text into record.exc_text
            record.exc_info = None  # to avoid Unpickleable error

        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):
        time.sleep(self.polltime+1) # give some time for messages to enter the queue.
        self.shutdown = True
        time.sleep(self.polltime+1) # give some time for the server to time out and see the shutdown

    def __del__(self):
        self.close() # hopefully this aids in orderly shutdown when things are going poorly.

def f(x):
    # just a logging command...
    logging.critical('function number: ' + str(x))
    # to make some calls take longer than others, so the output is "jumbled" as real MP programs are.
    time.sleep(x % 3)

def initPool(queue, level):
    """
    This causes the logging module to be initialized with the necessary info
    in pool threads to work correctly.
    """
    logging.getLogger('').addHandler(MultiProcessingLogHandler(logging.StreamHandler(), queue, child=True))
    logging.getLogger('').setLevel(level)

if __name__ == '__main__':
    stream = StringIO.StringIO()
    logQueue = multiprocessing.Queue(100)
    handler= MultiProcessingLogHandler(logging.StreamHandler(stream), logQueue)
    logging.getLogger('').addHandler(handler)
    logging.getLogger('').setLevel(logging.DEBUG)

    logging.debug('starting main')

    # when bulding the pool on a Windows machine we also have to init the logger in all the instances with the queue and the level of logging.
    pool = multiprocessing.Pool(processes=10, initializer=initPool, initargs=[logQueue, logging.getLogger('').getEffectiveLevel()] ) # start worker processes
    pool.map(f, range(0,50))
    pool.close()

    logging.debug('done')
    logging.shutdown()
    print "stream output is:"
    print stream.getvalue()

1
かしらif 'MainProcess' == multiprocessing.current_process().name:渡すの代わりに使用することができますかchild
mlt 2013年

Windowsで別のプロセスオブジェクトの代わりにプロセスプールを使用しようとしている場合、直接ピックルできないため、Managerを使用してサブプロセスにキューを渡す必要があることに言及する価値があります。
mlt 2013年

この実装は私にはうまくいきました。任意の数のハンドラで動作するように変更しました。この方法で、マルチハンドラー以外の方法でルートハンドラーを構成し、キューを安全に作成し、ルートハンドラーをこれに渡して削除し、これを唯一のハンドラーにすることができます。
Jaxor24 2015

3

ロガーのインスタンスをどこかに公開するだけです。そうすることで、他のモジュールやクライアントはAPIを使用して、ロガーを取得する必要がなくなりimport multiprocessingます。


1
この問題は、マルチプロセッシングロガーに名前が付けられていないように見えるため、メッセージストリームを簡単に解読できないことです。おそらく、作成後にそれらに名前を付けることが可能になるでしょう。
cdleary 2009年

モジュールごとに1つのロガーを公開するか、モジュール名でロガーを使用する別のクロージャーをエクスポートします。重要なのは、他のモジュールでAPIを使用できるようにすることです
Javier

間違いなくリーズナブルです(そして私から+1します!)が、import logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')どこからでも適切に機能することはできません。
cdleary 2009年

3
Pythonを使用するときに目にする興味深い現象です。他の言語でのシンプルで論理的なアプローチ(たとえば、マルチプロセッシングロガーの公開やラップ)のように、1行または2行のシンプルな行でやりたいことができるようになります。それをアクセサーに入れます)、それでも負担のように感じます。:)
カイロタン2009年

3

私はzzzeekの答えが好きでした。複数のスレッド/プロセスが同じパイプエンドを使用してログメッセージを生成すると、文字化けするため、キューの代わりにパイプを使用します。


ハンドラーで問題が発生していましたが、メッセージが文字化けすることはありませんでしたが、全体が機能しなくなりました。PipeをQueueに変更しました。しかし、私が得たエラーはそれによって解決されませんでした-最終的にreceive()メソッドにtry / exceptを追加しました-非常にまれに、例外をログに記録する試みが失敗し、そこに巻き込まれることになります。try / exceptを追加すると、何週間も問題なく実行され、standarderrファイルは1週間に約2つの誤った例外を取得します。
zzzeek 2009年

2

すべてのログをキューからすべてのログエントリを読み取る別のプロセスに委任するのはどうですか。

LOG_QUEUE = multiprocessing.JoinableQueue()

class CentralLogger(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue
        self.log = logger.getLogger('some_config')
        self.log.info("Started Central Logging process")

    def run(self):
        while True:
            log_level, message = self.queue.get()
            if log_level is None:
                self.log.info("Shutting down Central Logging process")
                break
            else:
                self.log.log(log_level, message)

central_logger_process = CentralLogger(LOG_QUEUE)
central_logger_process.start()

LOG_QUEUEをマルチプロセスメカニズムまたは継承によって共有するだけで、すべて正常に機能します。


1

私のコードの一部でlogging.exceptionを使用していることを除いて、アイアンハッカーに似たソリューションがあります。トレースバックはピクル可能ではないため、例外をキューに渡す前にフォーマットする必要があることがわかりました。

class QueueHandler(logging.Handler):
    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue
    def emit(self, record):
        if record.exc_info:
            # can't pass exc_info across processes so just format now
            record.exc_text = self.formatException(record.exc_info)
            record.exc_info = None
        self.queue.put(record)
    def formatException(self, ei):
        sio = cStringIO.StringIO()
        traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
        s = sio.getvalue()
        sio.close()
        if s[-1] == "\n":
            s = s[:-1]
        return s

私はこれらの線に沿って完全な例をここに見つけました。
Aryeh Leib Taurog、2012

1

以下は、Windows環境で使用できるクラスで、ActivePythonが必要です。他のロギングハンドラー(StreamHandlerなど)を継承することもできます。

class SyncronizedFileHandler(logging.FileHandler):
    MUTEX_NAME = 'logging_mutex'

    def __init__(self , *args , **kwargs):

        self.mutex = win32event.CreateMutex(None , False , self.MUTEX_NAME)
        return super(SyncronizedFileHandler , self ).__init__(*args , **kwargs)

    def emit(self, *args , **kwargs):
        try:
            win32event.WaitForSingleObject(self.mutex , win32event.INFINITE)
            ret = super(SyncronizedFileHandler , self ).emit(*args , **kwargs)
        finally:
            win32event.ReleaseMutex(self.mutex)
        return ret

そして、これは使用法を示す例です:

import logging
import random , time , os , sys , datetime
from string import letters
import win32api , win32event
from multiprocessing import Pool

def f(i):
    time.sleep(random.randint(0,10) * 0.1)
    ch = random.choice(letters)
    logging.info( ch * 30)


def init_logging():
    '''
    initilize the loggers
    '''
    formatter = logging.Formatter("%(levelname)s - %(process)d - %(asctime)s - %(filename)s - %(lineno)d - %(message)s")
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    file_handler = SyncronizedFileHandler(sys.argv[1])
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

#must be called in the parent and in every worker process
init_logging() 

if __name__ == '__main__':
    #multiprocessing stuff
    pool = Pool(processes=10)
    imap_result = pool.imap(f , range(30))
    for i , _ in enumerate(imap_result):
        pass

おそらくmultiprocessing.Lock()、Windows Mutexの代わりに使用すると、ソリューションが移植可能になります。
xmedeko

1

これが私の簡単なハック/回避策です...最も包括的ではありませんが、これを書く前に私が見つけた他のどの答えよりも簡単に変更でき、読みやすく理解しやすいと思います:

import logging
import multiprocessing

class FakeLogger(object):
    def __init__(self, q):
        self.q = q
    def info(self, item):
        self.q.put('INFO - {}'.format(item))
    def debug(self, item):
        self.q.put('DEBUG - {}'.format(item))
    def critical(self, item):
        self.q.put('CRITICAL - {}'.format(item))
    def warning(self, item):
        self.q.put('WARNING - {}'.format(item))

def some_other_func_that_gets_logger_and_logs(num):
    # notice the name get's discarded
    # of course you can easily add this to your FakeLogger class
    local_logger = logging.getLogger('local')
    local_logger.info('Hey I am logging this: {} and working on it to make this {}!'.format(num, num*2))
    local_logger.debug('hmm, something may need debugging here')
    return num*2

def func_to_parallelize(data_chunk):
    # unpack our args
    the_num, logger_q = data_chunk
    # since we're now in a new process, let's monkeypatch the logging module
    logging.getLogger = lambda name=None: FakeLogger(logger_q)
    # now do the actual work that happens to log stuff too
    new_num = some_other_func_that_gets_logger_and_logs(the_num)
    return (the_num, new_num)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    m = multiprocessing.Manager()
    logger_q = m.Queue()
    # we have to pass our data to be parallel-processed
    # we also need to pass the Queue object so we can retrieve the logs
    parallelable_data = [(1, logger_q), (2, logger_q)]
    # set up a pool of processes so we can take advantage of multiple CPU cores
    pool_size = multiprocessing.cpu_count() * 2
    pool = multiprocessing.Pool(processes=pool_size, maxtasksperchild=4)
    worker_output = pool.map(func_to_parallelize, parallelable_data)
    pool.close() # no more tasks
    pool.join()  # wrap up current tasks
    # get the contents of our FakeLogger object
    while not logger_q.empty():
        print logger_q.get()
    print 'worker output contained: {}'.format(worker_output)

1

この素晴らしいパッケージがあります

パッケージ:https : //pypi.python.org/pypi/multiprocessing-logging/

コード:https : //github.com/jruere/multiprocessing-logging

インストール:

pip install multiprocessing-logging

それから加えて:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
このライブラリは、文字通り、現在のSO投稿に関する別のコメントに基づいています:stackoverflow.com/a/894284/1698058
Chris Hunt、

起源:stackoverflow.com/a/894284/1663382ホームページのドキュメントに加えて、モジュールの使用例に感謝しています。
Liquidgenius

0

代替策の1つは、既知のファイルにマルチプロセッシングログを書き込み、atexitそれらのプロセスに参加するハンドラーを登録して、stderrでそれを読み戻すことです。ただし、そのようにstderrの出力メッセージにリアルタイムのフローを取得することはできません。


あなたがここにあなたのコメントからのものと同一の下に提案されたアプローチであるstackoverflow.com/questions/641420/...
iruvar

0

loggingモジュールのロック、スレッド、フォークの組み合わせでデッドロックが発生している場合は、バグレポート6721で報告されます(関連するSO質問も参照)。

ここに小さな修正ソリューションが投稿されています

ただし、これはの潜在的なデッドロックを修正するだけloggingです。それは物事がおそらく文字化けしていることを修正しません。ここに提示されている他の回答を参照してください。


0

上記の最も簡単なアイデア:

  • 現在のプロセスのファイル名とプロセスIDを取得します。
  • を設定します[WatchedFileHandler][1]。このハンドラーの理由については、ここで詳しく説明しますが、簡単に言うと、他のロギングハンドラーとの競合状態が悪化します。これは、競合状態の最短ウィンドウです。
    • / var / log / ...などのログを保存するパスを選択します。

0

これを必要とする可能性がある人のために、現在のプロセス名をログに追加するmultiprocessing_loggingパッケージのデコレーターを作成したので、誰が何をログに記録するかが明らかになります。

また、install_mp_handler()を実行するため、プールを作成する前に実行するのは役に立ちません。

これにより、どのワーカーがどのログメッセージを作成したかを確認できます。

ここに例を示す青写真があります:

import sys
import logging
from functools import wraps
import multiprocessing
import multiprocessing_logging

# Setup basic console logger as 'logger'
logger = logging.getLogger()
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(u'%(asctime)s :: %(levelname)s :: %(message)s'))
logger.setLevel(logging.DEBUG)
logger.addHandler(console_handler)


# Create a decorator for functions that are called via multiprocessing pools
def logs_mp_process_names(fn):
    class MultiProcessLogFilter(logging.Filter):
        def filter(self, record):
            try:
                process_name = multiprocessing.current_process().name
            except BaseException:
                process_name = __name__
            record.msg = f'{process_name} :: {record.msg}'
            return True

    multiprocessing_logging.install_mp_handler()
    f = MultiProcessLogFilter()

    # Wraps is needed here so apply / apply_async know the function name
    @wraps(fn)
    def wrapper(*args, **kwargs):
        logger.removeFilter(f)
        logger.addFilter(f)
        return fn(*args, **kwargs)

    return wrapper


# Create a test function and decorate it
@logs_mp_process_names
def test(argument):
    logger.info(f'test function called via: {argument}')


# You can also redefine undecored functions
def undecorated_function():
    logger.info('I am not decorated')


@logs_mp_process_names
def redecorated(*args, **kwargs):
    return undecorated_function(*args, **kwargs)


# Enjoy
if __name__ == '__main__':
    with multiprocessing.Pool() as mp_pool:
        # Also works with apply_async
        mp_pool.apply(test, ('mp pool',))
        mp_pool.apply(redecorated)
        logger.info('some main logs')
        test('main program')

-5

数十年で同じ問題に遭遇し、このサイトでこの質問を見つけた私の子供たちに、私はこの答えを残します。

シンプルさと過度に複雑。他のツールを使用してください。Pythonは素晴らしいですが、いくつかのことをするように設計されていません。

次のlogrotateデーモンのスニペットは私にとっては機能し、複雑すぎません。毎時間実行するようにスケジュールし、

/var/log/mylogfile.log {
    size 1
    copytruncate
    create
    rotate 10
    missingok
    postrotate
        timeext=`date -d '1 hour ago' "+%Y-%m-%d_%H"`
        mv /var/log/mylogfile.log.1 /var/log/mylogfile-$timeext.log
    endscript
}

これは私がそれをインストールする方法です(シンボリックリンクはlogrotateでは機能しません):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.