Python'da çoklu işleme kullanırken nasıl giriş yapmalıyım?


239

Şu anda Python 2.6 multiprocessingmodülünü kullanarak birden çok işlemi ortaya çıkaran bir çerçevede merkezi bir modülüm var . Kullandığı için multiprocessing, modül düzeyinde çoklu işleme duyarlı günlük vardır LOG = multiprocessing.get_logger(). Başına docs , bu kaydedicisi sen garble şeyler kadar yok ki süreç-paylaşımlı kilidi vardır sys.stderreşzamanlı yazmadan çoklu süreçleri alarak (ya da her türlü dosya tanıtıcısından).

Şu anda sahip olduğum sorun, çerçevedeki diğer modüllerin çok işlemciliğe duyarlı olmaması. Gördüğüm gibi, bu merkezi modüldeki tüm bağımlılıkları çok işlemcili günlüğe kaydetmeyi kullanmam gerekiyor. Çerçevenin tüm müşterileri için, bu çerçeve içinde sinir bozucu . Düşünmediğim alternatifler var mı?


10
Bağlandığınız dokümanlar, söylediklerinizin tam tersini belirtir, kaydedicinin süreç paylaşılan kilitleri yoktur ve işler karışır - benim de bir sorunum vardı.
Sebastian Blask

3
stdlib belgelerindeki örneklere bakın: Birden çok işlemden tek bir dosyaya oturum açma . Tarifler, diğer modüllerin çoklu işleme duyarlı olmasını gerektirmez.
jfs

Peki, kullanım durumu ne için multiprocessing.get_logger()? Günlüğe kaydetmenin bu diğer yollarına dayandırıldığı gibi multiprocessing, çok az değerdeki günlük işlevselliği vardır .
Tim Ludwinski

4
get_logger()multiprocessingmodülün kendisi tarafından kullanılan logger . Bir multiprocessingsorunu ayıklamak istiyorsanız faydalıdır .
jfs

Yanıtlar:


69

Bu müdahaleci olmayan ile başa çıkmanın tek yolu:

  1. Her çalışan işlemini, günlüğü farklı bir dosya tanımlayıcıya (diske veya boruya) gidecek şekilde yumurtlayın. İdeal olarak, tüm günlük girişleri zaman damgası olmalıdır.
  2. Denetleyici işleminiz bundan sonra aşağıdakilerden birini yapabilir :
    • Disk dosyaları kullanılıyorsa: İşlemin sonunda günlük dosyalarını zaman damgasına göre sıralayarak birleştirin
    • Borular kullanılıyorsa (önerilir): Günlük girişlerini anında tüm borulardan merkezi bir günlük dosyasına birleştirin. (Örn select. Boruların dosya tanımlayıcılarından periyodik olarak, kullanılabilir günlük girişlerinde birleştirme-sıralama gerçekleştirin ve merkezi günlükle aynı hizaya getirin.

Güzel, ben bunu düşünmeden önce 35s oldu (ben kullanmak düşündüm atexit:-). Sorun, size gerçek zamanlı bir okuma vermeyecek olmasıdır. Bu, çoklu iş parçacığının aksine çok işlemciliğin fiyatının bir parçası olabilir.
cdleary

@cdleary, borulu yaklaşımı kullanarak elde edebileceği kadar gerçek zamanlı olurdu (özellikle stderr ortaya çıkan süreçlerde arabelleğe
alınmadıysa

1
Bu arada, burada büyük varsayım: Windows değil. Windows'da mısınız?
vladr

22
Neden ana işlemde yalnızca bir çoklu işleme.Kalite ve günlük kaydı iş parçacığı kullanmıyoruz? Daha basit görünüyor.
Brandon Rhodes

1
@BrandonRhodes - Söylediğim gibi, müdahaleci olmayan . Kullanılacak multiprocessing.Queueyeniden multiprocessing.Queue
kablolama

122

Şimdi, her şeyi bir boru yoluyla ebeveyn sürecine besleyen bir günlük işleyici yazdım. Sadece on dakikadır test ediyorum ama gayet iyi çalışıyor.

( Not: Bu RotatingFileHandlerbenim kendi kullanım durumum olan sabit kodlanmıştır .)


Güncelleme: @javier artık bu yaklaşımı Pypi'de mevcut bir paket olarak sürdürüyor - Pypi'de github'da çoklu işleme günlüğüne bakın , https://github.com/jruere/multiprocessing-logging adresindeki github


Güncelleme: Uygulama!

Bu artık eşzamanlılığın doğru kullanımı için bir kuyruk kullanıyor ve ayrıca hataları doğru bir şekilde kurtarıyor. Bunu birkaç aydır üretimde kullanıyorum ve aşağıdaki mevcut sürüm sorunsuz çalışıyor.

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
Yukarıdaki işleyici, üst işlemden tüm dosya yazma işlemini gerçekleştirir ve alt işlemlerden iletilen iletileri almak için yalnızca bir iş parçacığı kullanır. İşleyicinin kendisini bir doğmuş çocuk işleminden çağırırsanız, bu yanlış kullanır ve RotatingFileHandler ile aynı sorunları alırsınız. Ben yukarıdaki kodu hiçbir sorun olmadan yıllarca kullandım.
zzzeek

9
Ne yazık ki bu yaklaşım Windows üzerinde çalışmaz. Gönderen docs.python.org/library/multiprocessing.html 16.6.2.12 "Not, Windows alt işlemler sadece üst sürecin kaydedicisi seviyesine devralır edeceğini - kaydedicisi başka özelleştirme miras edilmeyecektir." Alt işlemler işleyiciyi devralmaz ve seçilemez olduğu için açıkça iletemezsiniz.
Noah Yetter

2
İçinde multiprocessing.Queuebir iplik kullanan kayda değer put(). Bu nedenle, tüm alt işlemleri oluşturmadan önce başlatmayın put(yani MultiProcessingLogişleyiciyi kullanarak bir msg günlüğe kaydetme). Aksi takdirde iplik çocuk işleminde ölecektir. Bir çözüm, Queue._after_fork()her bir alt sürecin başlangıcında çağrı yapmak veya multiprocessing.queues.SimpleQueuebunun yerine, iş parçacığı içermeyen, ancak engelleme olan kullanımıdır.
Danqi Wang

5
Varsayımsal bir alt süreçten kullanımın yanı sıra başlatmayı gösteren basit bir örnek ekleyebilir misiniz? Alt işlemin, sınıfınızın başka bir örneğini oluşturmadan kuyruğa nasıl erişmesi gerektiği konusunda emin değilim.
JesseBuesking

11
@zzzeek, ​​bu çözüm iyi ama onunla bir paket bulamadım ya da benzer bir şey bu yüzden bir tane oluşturdum multiprocessing-logging.
Javier

30

QueueHandlerPython 3.2 ve sonraki sürümlerinde yereldir ve tam olarak bunu yapar. Önceki sürümlerde kolayca çoğaltılır.

Python belgelerinin iki tam örneği vardır: Birden çok işlemden tek bir dosyaya oturum açma

Python <3.2 kullananlar QueueHandleriçin https://gist.github.com/vsajip/591589 adresinden kendi kodunuza kopyalamanız veya alternatif olarak logutils'i içe aktarmanız yeterlidir .

Her işlem (üst işlem dahil) günlüğe kaydetme işlemini gerçekleştirir Queueve ardından bir listeneriş parçacığı veya işlem (her biri için bir örnek verilir) bunları alır ve yolsuzluk veya gargara riski yoktur.


21

Aşağıda, Google'dan buraya gelen herkes (benim gibi) için sadeliğe odaklanan başka bir çözüm var. Günlüğe kaydetme kolay olmalı! Sadece 3.2 veya üstü için.

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
QueueHandlerVe QueueListenersınıflar içinde, hem de Python 2.7 üzerinde mevcut kullanılabilir logutilspaketin.
Lev Levitsky

5
Ana işlem kaydedicisi de bir QueueHandler kullanmalıdır. Mevcut kodunuzda, ana işlem kuyruğu atlamaktır, böylece ana süreç ile işçiler arasında yarış koşulları olabilir. Herkes kuyruğa (QueueHandler aracılığıyla) giriş yapmalı ve StreamHandler'a sadece QueueListener giriş yapmalıdır.
Ismael EL ATIFI

Ayrıca, her çocukta kaydediciyi başlatmanız gerekmez. Kaydediciyi üst işlemde başlatın ve her alt işlemde kaydediciyi alın.
okwap

20

Yine başka bir alternatif, loggingpaketteki dosya tabanlı olmayan çeşitli günlük işleyicileri olabilir :

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(ve diğerleri)

Bu şekilde, güvenli bir şekilde yazabileceğiniz ve sonuçları doğru şekilde işleyebileceğiniz bir yerde kolayca bir günlük kayıt programına sahip olabilirsiniz. (Örneğin, mesajı seçip kendi dönen dosya işleyicisine yayan basit bir soket sunucusu.)

Bu SyslogHandlersizin için de ilgilenir. Tabii ki, syslogsistem örneğini değil kendi örneğinizi kullanabilirsiniz .


13

Günlüğe kaydetme ve kuyruk iş parçacığını ayrı tutan diğer bir varyant.

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

Kuyruk kaydından kaydedici adını alma fikrini seviyorum. Bu, fileConfig()MainProcess'te geleneksel ve PoolWorkers'da zar zor yapılandırılmış bir günlükçünün kullanılmasına izin verir (yalnızca setLevel(logging.NOTSET)). Başka bir yorumda belirtildiği gibi, havuz kullanıyorum, bu yüzden turşu böylece çok işlem yerine benim kuyruk (proxy) Yöneticisi'nden almak zorunda kaldı. Bu, kuyruğu sözlük içinde bir işçiye geçirmeme izin veriyor (çoğu argsparse nesnesini kullanarak türetildi vars()). Sonunda bu çatal () eksik ve @zzzeak çözümünü kıran MS Windows için en iyi yaklaşım gibi hissediyorum.
mlt

@mlt Yönetici kullanmak yerine init'e çok işlemcili bir Kuyruk da koyabileceğinizi düşünüyorum ( stackoverflow.com/questions/25557686/… için cevaba bakın - Kilitlerle ilgili, ancak Kuyruklar için de işe yaradığına inanıyorum)
fantabolous

@fantabolous Bu, MS Windows veya eksik olan başka bir platformda çalışmaz fork. Bu şekilde her sürecin kendi bağımsız yararsız kuyruğu olur. Bağlantılı soru-cevaptaki ikinci yaklaşım bu tür platformlarda işe yaramayacaktır. Taşınabilir olmayan kodların bir yoludur.
mlt

@mlt İlginç. Windows kullanıyorum ve benim için tamam gibi görünüyor - en son yorum yapmadan kısa bir süre sonra multiprocessing.Queueana süreçle paylaşan bir süreç havuzu oluşturdum ve o zamandan beri sürekli kullanıyorum. Neden çalıştığını anlamayacak.
muhteşem

10

Mevcut tüm çözümler, bir işleyici kullanılarak günlük yapılandırmasına çok fazla bağlıdır. Çözümüm aşağıdaki mimariye ve özelliklere sahip:

  • Sen kullanabilirsiniz herhangi istediğiniz günlük yapılandırmasını
  • Günlük kaydı bir daemon iş parçacığında yapılır
  • Bağlama yöneticisi kullanarak arka plan programının güvenli bir şekilde kapatılması
  • Günlüğe kaydetme iş parçacığı ile iletişim multiprocessing.Queue
  • Alt işlemlerde logging.Logger(ve daha önce tanımlanmış örnekler) tüm kayıtları kuyruğa göndermek için yamalı
  • Yeni : dekapaj hatalarını önlemek için kuyruğa göndermeden önce geri izleme ve mesajı formatlayın

Kullanım örneği ve çıktıya sahip kodu aşağıdaki Gist'te bulabilirsiniz: https://gist.github.com/schlamar/7003737


Bir şey eksik olmadıkça, bu aslında bir daemon iş parçacığı değildir, asla ayarlamadığınız daemon_thread.daemoniçin True. Bağlam yöneticisinde bir istisna oluştuğunda Python programımın düzgün çıkmasını sağlamak için bunu yapmam gerekiyordu.
blah238

Ayrıca hedef tarafından atılan yakalamak, günlük ve yutma istisnalara ihtiyaç funciçinde logged_callaksi istisna günlüğe kaydedilen diğer çıkışı ile bozuk alacağı. İşte bunun değiştirilmiş sürümü: gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

Çok işlemcili günlüğü birçok yayıncı ve bir abone (dinleyici) olarak temsil edebildiğimiz için , PUB-SUB mesajlarını uygulamak için ZeroMQ'yu kullanmak gerçekten bir seçenektir.

Ayrıca, ZMQ için Python bağlamaları olan PyZMQ modülü, günlük iletilerini bir zmq.PUB soketi üzerinde yayınlamak için nesne olan PUBHandler'ı uygular .

Web'de , PyZMQ ve PUBHandler kullanarak dağıtılmış uygulamadan merkezi günlük kaydı için, birden çok yayınlama işlemiyle yerel olarak çalışmak için kolayca benimsenebilen bir çözüm var .

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'in cevabını da seviyorum ama Andre, gargarayı önlemek için bir kuyruk gerekli olduğu konusunda haklı. Boru ile biraz şansım vardı, ama biraz beklenen garbling gördün. Uygulamanın düşündüğümden daha zor olduğu ortaya çıktı, özellikle de global değişkenler ve şeyler hakkında bazı ek kısıtlamaların bulunduğu Windows'da çalıştığından dolayı (bkz: Python Çoklu İşlemi Windows'ta Nasıl Uygulanır? )

Ama sonunda işe yaradı. Bu örnek muhtemelen mükemmel değildir, bu nedenle yorum ve önerilerinizi bekliyoruz. Ayrıca biçimlendiricinin veya kök kaydediciden başka bir şeyin ayarlanmasını desteklemez. Temel olarak, havuz işlemlerinin her birinde kaydediciyi kuyrukla yeniden başlatmanız ve günlükçideki diğer öznitelikleri ayarlamanız gerekir.

Yine, kodun nasıl daha iyi hale getirileceğine dair herhangi bir öneri kabul edilir. Kesinlikle tüm Python hilelerini bilmiyorum :-)

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
Acaba if 'MainProcess' == multiprocessing.current_process().name:geçiş yerine kullanılabilir childmi?
mlt

Bu durumda bir başkası Windows üzerinde kullanımı süreci havuzunun yerine ayrı işlem nesnelere çalışıyor, bu bahsetmemiz Yöneticisi değil picklable doğrudan olduğu gibi subprocesses için sıraya geçmek için kullanılacaktır.
mlt

Bu uygulama benim için iyi çalıştı. Rasgele sayıda işleyiciyle çalışacak şekilde değiştirdim. Bu şekilde, kök işleyiciyi çok işlemeli olmayan bir şekilde yapılandırabilirsiniz, daha sonra kuyruğu oluşturmanın, kök işleyicilerini buna geçirmenin, silmenin ve bunu tek işleyici yapmanın güvenli olduğu yerlerde yapılandırabilirsiniz.
Jaxor24

3

Logger örneğinizi bir yerde yayınlamanız yeterlidir. bu şekilde, diğer modüller ve istemciler günlükçüyü almak zorunda kalmadan API'nızı kullanabilir import multiprocessing.


1
Buradaki sorun, çok işlemcili günlüklerin adsız görünmesidir, bu nedenle mesaj akışını kolayca deşifre edemezsiniz. Belki onları yarattıktan sonra adlandırmak mümkün olur, bu da bakmayı daha makul hale getirir.
cdleary

her modül için bir logger yayınlayın veya daha iyisi logger'ı modül adıyla kullanan farklı kapanışları dışa aktarın. önemli olan diğer modüllerin API'nızı kullanmasına izin vermektir
Javier

Kesinlikle makul (ve + 1 benden!), Ama ben sadece import logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')her yerden mümkün ve düzgün çalışması için özledim özledim .
cdleary

3
Python'u kullandığımda, 1 veya 2 basit satırda istediğimizi yapabilmeye alıştığımız ilginç bir fenomen, diğer dillerde basit ve mantıklı yaklaşım (örneğin, çok işlemcili günlükçüyü yayınlamak veya sarmak için) bir erişimcide) hala bir yük gibi hissediyor. :)
Kylotan

3

Zzzeek'in cevabını beğendim. Birden çok iş parçacığı / süreç aynı boru ucunu günlük iletileri oluşturmak için kullanırlar, çünkü onlar bir boru için yerine sadece bir sıra Boru olurdu.


Mesajlar bozuk olsa da, işleyiciyle ilgili bazı sorunlar yaşıyordum, sadece her şey çalışmayı durduracaktı. Pipe'ı daha uygun olduğu için Queue olarak değiştirdim. Ancak aldığım hatalar bu şekilde çözülmedi - sonuçta almak () yöntemi dışında bir deneme / ekledim - çok nadiren, istisnaları günlüğe kaydetme girişimi başarısız olur ve orada yakalanır. Deneme / hariç ekledikten sonra, sorunsuz bir şekilde haftalarca çalışır ve bir standarderr dosyası haftada yaklaşık iki hatalı istisna yakalar.
09:51

2

Tüm günlük kaydını bir Kuyruktaki tüm günlük girdilerini okuyan başka bir işleme devretmeye ne dersiniz?

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'yu çok işlemli mekanizmalardan veya hatta kalıtımdan herhangi biriyle paylaşmanız yeterlidir ve her şey yolunda gider!


1

Bazı kodumda logging.exception kullanmak ve izleme geri dönüşü pickle'able olmadığından istisna yeniden sıraya geçmeden önce biçimlendirmek gerekiyordu dışında ironhacker benzer bir çözüm var:

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

Burada bu hatlar boyunca eksiksiz bir örnek buldum .
Aryeh Leib Taurog

1

Aşağıda Windows ortamında kullanılabilen, ActivePython gerektiren bir sınıf bulunmaktadır. Ayrıca diğer günlük işleyicileri (StreamHandler vb.) İçin miras alabilirsiniz

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

Ve kullanımı gösteren bir örnek:

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

Muhtemelen multiprocessing.Lock()Windows Mutex yerine kullanmak çözümü taşınabilir hale getirecektir.
xmedeko

1

İşte benim basit kesmek / geçici çözüm ... en kapsamlı değil, ama kolayca değiştirilebilir ve okunması ve anlaşılması daha basit Ben bunu yazmadan önce bulduğum diğer cevaplardan daha düşünüyorum:

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

Bu harika paket var

Paket: https://pypi.python.org/pypi/multiprocessing-logging/

kodu: https://github.com/jruere/multiprocessing-logging

Yüklemek:

pip install multiprocessing-logging

Sonra Ekle:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
Bu kütüphane kelimenin tam anlamıyla şu andaki SO yazısına ilişkin başka bir yoruma dayanmaktadır: stackoverflow.com/a/894284/1698058 .
Chris Hunt

Kökenleri: stackoverflow.com/a/894284/1663382 Ana sayfadaki belgelere ek olarak modülün örnek kullanımını takdir ediyorum.
Liquidgenius

0

Alternatiflerden biri, çoklu işlem günlüğünü bilinen bir dosyaya yazmak ve atexitbu işlemlere katılmak için işleyiciyi stderr'de yeniden okumaktır; ancak, bu şekilde stderr'deki çıkış mesajlarına gerçek zamanlı bir akış elde edemezsiniz.


Aşağıda önerdiğiniz yaklaşım, buradaki yorumunuzla aynıdır. stackoverflow.com/questions/641420/…
iruvar

0

loggingModüldeki kilitler, dişler ve çatalların bir kombinasyonunda meydana gelen kilitlenmeler varsa , bu hata raporu 6721'de bildirilir (ayrıca ilgili SO sorusuna bakın ).

Burada yayınlanan küçük bir düzeltme çözümü var .

Ancak, bu sadece olası kilitlenmeleri düzeltir logging. Bu, işlerin belki de bozuk olduğunu düzeltmeyecek. Burada sunulan diğer cevaplara bakın.


0

Bahsedildiği gibi en basit fikir:

  • Geçerli işlemin dosya adını ve işlem kimliğini alın.
  • Ayarlayın a [WatchedFileHandler][1]. Bu işleyicinin nedenleri burada ayrıntılı olarak tartışılmaktadır , ancak kısaca diğer günlük tutucularla bazı daha kötü yarış koşulları vardır. Bu yarış koşulu için en kısa pencereye sahip.
    • Günlükleri kaydetmek için / var / log / ... gibi bir yol seçin.

0

Buna kim ihtiyaç duyabilirse, günlüklere geçerli işlem adını ekleyen multiprocessing_logging paketi için bir dekoratör yazdım, böylece kimin neyi günlüğe kaydettiği anlaşılır.

Ayrıca, bir havuz oluşturmadan önce çalıştırmak için install_mp_handler () işlevini çalıştırır.

Bu, hangi çalışanın hangi günlükleri iletileri oluşturduğunu görmemi sağlar.

İşte bir örnek içeren taslak:

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

On yıllardır aynı meseleyi karşılayan ve bu soruyu bu sitede bulan çocuklarıma bu cevabı bırakıyorum.

Sadelik ve aşırı karmaşıklık. Sadece diğer araçları kullanın. Python harika, ama bazı şeyler yapmak için tasarlanmamıştı.

Logrotate daemon için aşağıdaki snippet benim için çalışıyor ve fazla karmaşık değil. Saatlik çalışacak şekilde zamanlayın ve

/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
}

Ben nasıl yüklüyorum (semboller logrotate için çalışmıyor):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.