Come devo accedere mentre utilizzo il multiprocessing in Python?


239

In questo momento ho un modulo centrale in un framework che genera più processi usando il multiprocessingmodulo Python 2.6 . Perché utilizza multiprocessing, v'è a livello di modulo Log multiprocessing-aware, LOG = multiprocessing.get_logger(). Secondo i documenti , questo logger ha blocchi condivisi dal processo in modo da non confondere le cose sys.stderr(o qualunque filehandle) avendo più processi che vi scrivono contemporaneamente.

Il problema che ho ora è che gli altri moduli nel framework non sono consapevoli del multiprocessore. Per come la vedo io, ho bisogno di fare in modo che tutte le dipendenze su questo modulo centrale utilizzino una registrazione compatibile con multiprocessing. È fastidioso all'interno del framework, figuriamoci per tutti i client del framework. Ci sono alternative a cui non sto pensando?


10
I documenti a cui ti colleghi, affermano l'esatto contrario di ciò che dici, il logger non ha alcun processo di blocchi condivisi e le cose si confondono - un problema che ho avuto anche.
Sebastian Blask,

3
vedere esempi nei documenti stdlib: registrazione in un singolo file da più processi . Le ricette non richiedono che altri moduli siano consapevoli del multiprocessore.
jfs,

Allora, a cosa serve multiprocessing.get_logger()? Sembra basato su questi altri modi di fare la registrazione che hanno una funzionalità di registrazione multiprocessingdi scarso valore.
Tim Ludwinski,

4
get_logger()è il logger utilizzato dal multiprocessingmodulo stesso. È utile se si desidera eseguire il debug di un multiprocessingproblema.
jfs,

Risposte:


69

L'unico modo per affrontarlo in modo non intrusivo è:

  1. Genera ogni processo di lavoro in modo tale che il suo registro vada a un descrittore di file diverso (su disco o su pipe.) Idealmente, tutte le voci di registro dovrebbero essere marcate con il timestamp.
  2. Il processo del controller può quindi effettuare una delle seguenti operazioni:
    • Se si utilizzano file su disco: unire i file di registro alla fine dell'esecuzione, ordinati per data e ora
    • Se si utilizzano pipe (consigliato): unire le voci di registro al volo da tutte le pipe, in un file di registro centrale. (Ad esempio, periodicamente selectdai descrittori di file delle pipe, eseguire unisci-ordinamento sulle voci di registro disponibili e scaricare il registro centralizzato. Ripetere.)

Bello, erano 35 anni prima che ci pensassi (pensavo che avrei usato atexit:-). Il problema è che non ti darà una lettura in tempo reale. Questo può far parte del prezzo del multiprocessing rispetto al multithreading.
cdleary,

@cdleary, usando l'approccio piped sarebbe quasi in tempo reale come si può ottenere (specialmente se stderr non è bufferizzato nei processi generati.)
vladr

1
Per inciso, grande presupposto qui: non Windows. Sei su Windows?
Vladr,

22
Perché non usare semplicemente un multiprocessing.Queue e un thread di registrazione nel processo principale? Sembra più semplice
Brandon Rodi,

1
@BrandonRhodes - Come ho detto, non intrusivamente . L'uso multiprocessing.Queuenon sarà più semplice se c'è un sacco di codice da ricablare da usare multiprocessing.Queuee / o se le prestazioni sono un problema
vladr

122

Ho appena scritto un mio gestore di log che invia semplicemente tutto al processo genitore tramite una pipe. L'ho provato solo per dieci minuti, ma sembra funzionare abbastanza bene.

( Nota: questo è hardcoded RotatingFileHandler, che è il mio caso d'uso.)


Aggiornamento: @javier ora mantiene questo approccio come pacchetto disponibile su Pypi - vedi multiprocessing-logging su Pypi, github su https://github.com/jruere/multiprocessing-logging


Aggiornamento: implementazione!

Questo ora utilizza una coda per la corretta gestione della concorrenza e inoltre recupera correttamente dagli errori. Lo sto usando in produzione da diversi mesi e la versione corrente qui sotto funziona senza problemi.

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
Il gestore sopra fa tutto il file che scrive dal processo genitore e usa solo un thread per ricevere messaggi passati da processi figlio. Se invochi il gestore stesso da un processo figlio generato, lo sta utilizzando in modo errato e otterrai tutti gli stessi problemi di RotatingFileHandler. Ho usato il codice sopra per anni senza problemi.
zzzeek,

9
Purtroppo questo approccio non funziona su Windows. Da docs.python.org/library/multiprocessing.html 16.6.2.12 "Nota che su Windows i processi figlio erediteranno solo il livello del logger del processo genitore - qualsiasi altra personalizzazione del logger non verrà ereditata." I sottoprocessi non erediteranno il gestore e non è possibile passarlo esplicitamente perché non è selezionabile.
Noah Yetter

2
Vale la pena notare che multiprocessing.Queueutilizza un thread per entrare put(). Quindi non invocare put(cioè registrare un messaggio usando il MultiProcessingLoggestore) prima di creare tutti i sottoprocessi. Altrimenti il ​​thread sarà morto nel processo figlio. Una soluzione è quella di chiamare Queue._after_fork()all'inizio di ogni processo figlio, o di utilizzare multiprocessing.queues.SimpleQueueinvece, che non coinvolge thread ma sta bloccando.
Danqi Wang,

5
Potresti aggiungere un semplice esempio che mostra l'inizializzazione, nonché l'utilizzo da un ipotetico processo figlio? Non sono sicuro di come il processo figlio dovrebbe ottenere l'accesso alla coda senza creare un'istanza di un'altra istanza della tua classe.
JesseBuesking,

11
@zzzeek, ​​questa soluzione è buona ma non sono riuscito a trovare un pacchetto con esso o qualcosa di simile, quindi ne ho creato uno chiamato multiprocessing-logging.
Javier,

30

QueueHandlerè nativo in Python 3.2+ e fa esattamente questo. È facilmente replicabile nelle versioni precedenti.

I documenti Python hanno due esempi completi: registrazione in un singolo file da più processi

Per coloro che usano Python <3.2, copia il QueueHandlertuo codice da: https://gist.github.com/vsajip/591589 o in alternativa importa i logutils .

Ogni processo (incluso il processo genitore) inserisce la propria registrazione su Queue, quindi un listenerthread o processo (viene fornito un esempio per ciascuno) raccoglie quelli e li scrive tutti su un file - nessun rischio di corruzione o confusione.


21

Di seguito è un'altra soluzione incentrata sulla semplicità per chiunque (come me) che arriva qui da Google. La registrazione dovrebbe essere facile! Solo per 3.2 o versioni successive.

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
Le classi QueueHandlere QueueListenerpossono essere utilizzate anche su Python 2.7, disponibile nel logutilspacchetto.
Lev Levitsky,

5
Il logger del processo principale dovrebbe anche usare un QueueHandler. Nel tuo codice attuale, il processo principale sta bypassando la coda in modo che possano esserci condizioni di competizione tra il processo principale e quelli dei lavoratori. Tutti dovrebbero accedere alla coda (tramite un QueueHandler) e solo il QueueListener dovrebbe poter accedere a StreamHandler.
Ismael EL ATIFI

Inoltre, non è necessario inizializzare il logger in ciascun figlio. È sufficiente inizializzare il logger nel processo padre e ottenere il logger in ciascun processo figlio.
Okwap

20

Un'altra alternativa potrebbe essere rappresentata dai vari gestori della registrazione non basati su file nel loggingpacchetto :

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(e altri)

In questo modo, potresti facilmente avere un demone di registrazione da qualche parte in cui puoi scrivere in modo sicuro e gestire correttamente i risultati. (Ad esempio, un semplice server socket che disimballa semplicemente il messaggio ed emette il proprio gestore di file rotante.)

Si SyslogHandleroccuperebbe di questo anche per te. Certo, potresti usare la tua istanza di syslog, non quella di sistema.


13

Una variante delle altre che mantiene separati il ​​thread di registrazione e di coda.

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

Mi piace l'idea di recuperare il nome del logger dal record della coda. Ciò consente di utilizzare convenzionale fileConfig()in MainProcess e un logger appena configurato in PoolWorkers (solo con setLevel(logging.NOTSET)). Come ho già detto in un altro commento, sto usando Pool, quindi ho dovuto ottenere la mia coda (proxy) da Manager invece di multiprocessing in modo che possa essere decapato. Questo mi permette di passare la coda a un lavoratore all'interno di un dizionario (la maggior parte dei quali deriva dall'oggetto argsparse usando vars()). Sento che alla fine questo è l'approccio migliore per MS Windows che manca di fork () e rompe la soluzione @zzzeak.
mlt

@mlt Penso che potresti anche mettere una coda multiprocessore nell'init invece di usare un Manager (vedi risposta a stackoverflow.com/questions/25557686/… - si tratta di Locks ma credo che funzioni anche per le code)
fantabolous

@fantabolous che non funzionerà su MS Windows o su qualsiasi altra piattaforma che manca fork. In questo modo ogni processo avrà la sua coda inutile indipendente. Il secondo approccio nel Q / A collegato non funzionerà su tali piattaforme. È un modo per codice non portatile.
mlt,

@mlt Interessante. Sto usando Windows e sembra funzionare bene per me - non molto tempo dopo l'ultimo commento ho creato un pool di processi condividendo un multiprocessing.Queueprocesso principale e da allora lo uso costantemente. Non pretendiamo di capire perché funzioni però.
fantabolous

10

Tutte le soluzioni attuali sono troppo accoppiate alla configurazione del logging usando un gestore. La mia soluzione ha la seguente architettura e funzionalità:

  • È possibile utilizzare qualsiasi configurazione di registrazione desiderata
  • La registrazione viene eseguita in un thread demone
  • Arresto sicuro del demone utilizzando un gestore di contesto
  • La comunicazione al thread di registrazione viene effettuata da multiprocessing.Queue
  • Nei sottoprocessi, logging.Logger(e istanze già definite) sono patchati per inviare tutti i record alla coda
  • Novità : formatta traceback e messaggio prima di inviarli in coda per evitare errori di decapaggio

Il codice con esempio di utilizzo e output è disponibile al seguente Gist: https://gist.github.com/schlamar/7003737


A meno che non mi manca qualcosa, questo non è in realtà un thread demone, dal momento che non hai mai impostato daemon_thread.daemona True. Ho dovuto farlo per far uscire correttamente il mio programma Python quando si verifica un'eccezione nel gestore del contesto.
blah238,

Ho anche bisogno di eccezioni di cattura, di registro e rondine generata dalla destinazione funcin logged_call, altrimenti l'eccezione sarebbe ottenere confuso con altre uscite Logged. Ecco la mia versione modificata di questo: gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

Dal momento che possiamo rappresentare la registrazione multiprocesso come molti editori e un abbonato (listener), utilizzare ZeroMQ per implementare la messaggistica PUB-SUB è davvero un'opzione.

Inoltre, il modulo PyZMQ , i collegamenti Python per ZMQ, implementa PUBHandler , che è oggetto per la pubblicazione di messaggi di registrazione su un socket zmq.PUB.

Esiste una soluzione sul Web , per la registrazione centralizzata da un'applicazione distribuita tramite PyZMQ e PUBHandler, che può essere facilmente adottata per lavorare localmente con più processi di pubblicazione.

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

Mi piace anche la risposta di zzzeek, ​​ma Andre ha ragione nel dire che è necessaria una coda per evitare confusione. Ho avuto un po 'di fortuna con la pipa, ma ho visto confusione che è in qualche modo prevista. L'implementazione si è rivelata più difficile di quanto pensassi, in particolare a causa dell'esecuzione su Windows, dove ci sono alcune restrizioni aggiuntive su variabili e cose globali (vedi: Come viene implementato Python Multiprocessing su Windows? )

Ma finalmente l'ho fatto funzionare. Questo esempio probabilmente non è perfetto, quindi commenti e suggerimenti sono i benvenuti. Inoltre, non supporta l'impostazione del formattatore o altro oltre al logger di root. Fondamentalmente, è necessario reinizializzare il logger in ciascuno dei processi del pool con la coda e impostare gli altri attributi sul logger.

Ancora una volta, qualsiasi suggerimento su come migliorare il codice è il benvenuto. Certamente non conosco ancora tutti i trucchi di 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
Mi chiedo se if 'MainProcess' == multiprocessing.current_process().name:può essere usato al posto del passaggio child?
mlt

Nel caso in cui qualcun altro stia tentando di utilizzare il pool di processi anziché oggetti di processo separati su Windows, vale la pena ricordare che Manager deve essere utilizzato per passare la coda ai sottoprocessi in quanto non è selezionabile direttamente.
mlt

Questa implementazione ha funzionato bene per me. L'ho modificato per funzionare con un numero arbitrario di gestori. In questo modo è possibile configurare il gestore root in modo non multiprocessore, quindi dove è sicuro creare la coda, passare i gestori root a questo, eliminarli e renderlo l'unico gestore.
Jaxor24,

3

pubblica da qualche parte la tua istanza del logger. in questo modo, gli altri moduli e client possono utilizzare la tua API per ottenere il logger senza doverlo fare import multiprocessing.


1
Il problema è che i logger multiprocessore appaiono senza nome, quindi non sarai in grado di decifrare facilmente il flusso di messaggi. Forse sarebbe possibile nominarli dopo la creazione, il che renderebbe più ragionevole guardare.
cdleary,

bene, pubblica un logger per ogni modulo, o meglio, esporta diverse chiusure che usano il logger con il nome del modulo. il punto è lasciare che altri moduli utilizzino la tua API
Javier

Decisamente ragionevole (e +1 da parte mia!), Ma mi mancherebbe riuscire a farlo import logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')da qualsiasi luogo e farlo funzionare correttamente.
cdleary,

3
È un fenomeno interessante che vedo quando uso Python, che ci abituiamo così tanto a poter fare ciò che vogliamo in 1 o 2 righe semplici che l'approccio semplice e logico in altre lingue (ad es. Per pubblicare il logger multiprocessore o avvolgere in un accessorio) sembra ancora un peso. :)
Kylotan,

3

Mi è piaciuta la risposta di zzzeek. Vorrei solo sostituire la pipe con una coda poiché se più thread / processi utilizzano la stessa estremità di pipe per generare messaggi di registro, verranno confusi.


Stavo avendo dei problemi con il gestore, anche se non era che i messaggi fossero confusi, è solo che tutto avrebbe smesso di funzionare. Ho cambiato Pipe per essere in coda poiché è più appropriato. Tuttavia gli errori che stavo ottenendo non sono stati risolti da questo - alla fine ho aggiunto un metodo try / tranne al metodo receive () - molto raramente, un tentativo di registrare le eccezioni fallirà e finirà per essere catturato lì. Una volta aggiunto il tentativo / tranne, viene eseguito per settimane senza problemi e un file standarderr acquisirà circa due eccezioni errate a settimana.
zzzeek,

2

Che ne dici di delegare tutta la registrazione a un altro processo che legge tutte le voci di registro da una coda?

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

Basta condividere LOG_QUEUE tramite uno qualsiasi dei meccanismi multiprocesso o anche l'ereditarietà e tutto funziona bene!


1

Ho una soluzione simile a Ironhacker, tranne per il fatto che utilizzo logging.exception in alcuni dei miei codici e ho scoperto che avevo bisogno di formattare l'eccezione prima di passarla di nuovo sulla coda poiché i traceback non sono selezionabili:

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

Ho trovato un esempio completo in questo senso qui .
Aryeh Leib Taurog,

1

Di seguito è una classe che può essere utilizzata in ambiente Windows, richiede ActivePython. È inoltre possibile ereditare per altri gestori di registrazione (StreamHandler ecc.)

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

Ed ecco un esempio che dimostra l'utilizzo:

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

Probabilmente l'utilizzo al multiprocessing.Lock()posto di Windows Mutex renderebbe portatile la soluzione.
xmedeko,

1

Ecco il mio semplice trucco / soluzione ... non il più completo, ma facilmente modificabile e più semplice da leggere e comprendere, penso di qualsiasi altra risposta che ho trovato prima di scrivere questo:

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

C'è questo fantastico pacchetto

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

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

Installare:

pip install multiprocessing-logging

Poi aggiungi:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
Questa libreria è letteralmente basata su un altro commento sull'attuale post SO: stackoverflow.com/a/894284/1698058 .
Chris Hunt,

Origini: stackoverflow.com/a/894284/1663382 Apprezzo l'uso esemplificativo del modulo, oltre alla documentazione sulla homepage.
Liquidgenius,

0

Una delle alternative è quella di scrivere la registrazione multi-process in un file noto e registrare un atexitgestore per unirsi a quei processi e rileggerlo su stderr; tuttavia, non otterrai un flusso in tempo reale per i messaggi di output su stderr in quel modo.


è l'approccio che proponete sotto identico a quello da qui il tuo commento stackoverflow.com/questions/641420/...
Iruvar

0

Se si verificano deadlock in una combinazione di blocchi, thread e fork nel loggingmodulo, questo viene riportato nella segnalazione errori 6721 (vedere anche la relativa domanda SO ).

C'è una piccola soluzione di correzione pubblicata qui .

Tuttavia, ciò risolverà qualsiasi potenziale deadlock in logging. Ciò non risolverà che le cose sono forse confuse. Vedi le altre risposte presentate qui.


0

L'idea più semplice come detto:

  • Prendi il nome file e l'ID processo del processo corrente.
  • Imposta a [WatchedFileHandler][1]. Le ragioni di questo gestore sono discusse in dettaglio qui , ma in breve ci sono alcune condizioni di gara peggiori con gli altri gestori di registrazione. Questo ha la finestra più breve per le condizioni di gara.
    • Scegli un percorso in cui salvare i log come / var / log / ...

0

Per chiunque possa averne bisogno, ho scritto un decoratore per il pacchetto multiprocessing_logging che aggiunge il nome del processo corrente ai registri, quindi diventa chiaro chi registra cosa.

Esegue anche install_mp_handler (), quindi diventa inutile eseguirlo prima di creare un pool.

Questo mi permette di vedere quale lavoratore crea quali registri messaggi.

Ecco il progetto con un esempio:

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

Ai miei figli che incontrano lo stesso problema da decenni e hanno trovato questa domanda su questo sito lascio questa risposta.

Semplicità vs complicazioni eccessive. Usa solo altri strumenti. Python è fantastico, ma non è stato progettato per fare alcune cose.

Il seguente frammento per il demone logrotate funziona per me e non complica le cose. Pianificalo per funzionare ogni ora e

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

Ecco come lo installo (i collegamenti simbolici non funzionano per logrotate):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.