2015-03-12 13 views
5

Supponiamo di aver configurato i gestori di registrazione nel processo principale. Il processo principale genera alcuni bambini ea causa di os.fork() (in Linux) tutti i logger e i gestori vengono ereditati dal processo principale. Nell'esempio che segue 'Hello World' verrebbero stampati 100 volte alla console:Come impedire l'ereditarietà di logger e gestori Python durante la multiprocessing basata su fork?

import multiprocessing as mp 
import logging 


def do_log(no): 
    # root logger logs Hello World to stderr (StreamHandler) 
    # BUT I DON'T WANT THAT! 
    logging.getLogger().info('Hello world {}'.format(no)) 


def main(): 
    format = '%(processName)-10s %(name)s %(levelname)-8s %(message)s' 

    # This creates a StreamHandler 
    logging.basicConfig(format=format, level=logging.INFO) 

    n_cores = 4 
    pool = mp.Pool(n_cores) 
    # Log to stdout 100 times concurrently 
    pool.map(do_log, range(100)) 
    pool.close() 
    pool.join() 


if __name__ == '__main__': 
    main() 

Questo stamperà qualcosa del tipo:

ForkPoolWorker-1 root INFO  Hello world 0 
ForkPoolWorker-3 root INFO  Hello world 14 
ForkPoolWorker-3 root INFO  Hello world 15 
ForkPoolWorker-3 root INFO  Hello world 16 
... 

Tuttavia, non voglio il processo figlio di ereditare tutto la configurazione di registrazione dal genitore. Quindi nell'esempio sopra do_log non dovrebbe stampare nulla su stderr perché non ci dovrebbe essere StreamHandler.

Come impedire di ereditare i registratori e i gestori senza rimuoverli o eliminarli nel processo padre originale?


EDIT: Sarebbe una buona idea per rimuovere semplicemente tutti i gestori durante l'inizializzazione della piscina?

def init_logging(): 
    for logger in logging.Logger.manager.loggerDict.values(): 
     if hasattr(logger, 'handlers'): 
      logger.handlers = [] 

e

pool = mp.Pool(n_cores, initializer=init_logging, initargs=()) 

Inoltre, posso anche tranquillamente close() tutti (file) i gestori durante la funzione di inizializzazione?

+0

'multiprocessing.Pool' ha una funzione di inizializzazione chiamata nel figlio ma non so come potrebbe disattivare tutti i gestori di registrazione senza un trucco sporco. – tdelaney

+0

Potrei semplicemente dire 'logging.shutdown(); logging.Logger.manager.loggerDict = {} 'nell'inizializzatore? O questo interferirebbe con i nuovi gestori che creerò in seguito? O essere hacky? – SmCaterpillar

+0

È stato definito per essere utilizzato all'uscita dall'applicazione, quindi non mi aspetto che sia una buona soluzione. Svuota anche tutti i buffer che sarebbero una brutta cosa (immagina un registratore di file). In effetti, sono sorpreso di non essermi imbattuto in questo problema da solo ... Non vedo l'ora di rispondere. Potrebbe essere una buona nuova funzionalità da aggiungere al logging. – tdelaney

risposta

4

Non è necessario per evitare che, basta riconfigurare la gerarchia di registrazione.

Penso che tu sia sulla strada giusta con l'inizializzatore del pool. Ma invece di provare a hackerare le cose, lascia che il pacchetto di logging faccia ciò che è stato progettato per fare. Lascia che il pacchetto di registrazione esegua la riconfigurazione della gerarchia di registrazione nei processi di lavoro.

Ecco un esempio:

def main(): 

    def configure_logging(): 
     logging_config = { 
      'formatters': { 
       'f': { 
        'format': '%(processName)-10s %(name)s' 
           ' %(levelname)-8s %(message)s', 
       }, 
      }, 
      'handlers': { 
       'h': { 
        'level':'INFO', 
        'class':'logging.StreamHandler', 
        'formatter':'f', 
       }, 
      }, 
      'loggers': { 
       '': { 
        'handlers': ['h'], 
        'level':'INFO', 
        'propagate': True, 
       }, 
      }, 
      'version': 1, 
     } 

     pname = mp.current_process().name 
     if pname != 'MainProcess': 
      logging_config['handlers'] = { 
       'h': { 
        'level':'INFO', 
        'formatter':'f', 
        'class':'logging.FileHandler', 
        'filename': pname + '.log', 
       }, 
      } 

     logging.config.dictConfig(logging_config) 

    configure_logging() # MainProcess 
    def pool_initializer(): 
     configure_logging() 

    n_cores = 4 
    pool = mp.Pool(n_cores, initializer=pool_initializer) 
    pool.map(do_log, range(100)) 
    pool.close() 
    pool.join() 

Ora, i processi di lavoro sarà ogni accedere ai propri file di log individuali, e non utilizzerà più stderr StreamHandler del processo principale.

2

La risposta più semplice è che probabilmente si dovrebbe evitare di modificare le globali con multiprocessing. Si noti che il logger principale, che si ottiene usando logging.getLogger(), è globale.

Il modo più semplice è creare una nuova istanza logging.Logger per ogni processo. È possibile assegnare un nome, dopo i processi, o semplicemente in modo casuale:

log= logging.getLogger(str(uuid.uuid4()))

Si consiglia inoltre di controllare how should I log while using multiprocessing in python

+0

Bene, voglio che ogni processo effettui il log in un nuovo file. Questo è il motivo per cui non voglio ereditare le configurazioni del logger. Inoltre c'è un gruppo di logger definiti dall'utente che esistono già. Quindi i taglialegna non sono solo da me ;-). Inoltre, voglio essere ancora in grado di avere una gerarchia di logging così qualcosa come 'log = logging.getLogger (str (uuid.uuid4()))' sicuramente non funzionerà. – SmCaterpillar

+0

@SmCaterpillar è possibile avere ciascun programma di registrazione in un file diverso semplicemente impostando i gestori appropriati. Ti consiglio di non avere una gerarchia di logging comune su processi diversi - avresti bisogno di implementare un meccanismo di blocco tra processi (vedi la risposta a cui mi sono collegato per i dettagli) – goncalopp

+0

Ancora non convinto :-). Perché non dovrei mantenere una gerarchia? Il punto è che ho qualcosa come un simulatore (più di questo, fondamentalmente un quadro di simulazione) che esegue diverse impostazioni dei parametri, di solito single core. Tuttavia, poiché tutte le esecuzioni dei parametri sono indipendenti, potrei di volta in volta passare alla multiprocessing. Pertanto, non desidero riscrivere la gerarchia di gestione e registrazione del logging, ma semplicemente farlo funzionare con il multiprocessing. Mi piace l'idea che ogni processo registri su un file diverso. Tuttavia, anche se scelgo quello, i miei vecchi gestori persistono ancora a causa del biforcuta :-(. – SmCaterpillar

0

Se avete bisogno per evitare che la gerarchia di registrazione venga ereditato nei processi di lavoro, è sufficiente fare la configurazione di registrazione dopo la creazione del pool di lavoro.Dal tuo esempio:

pool = mp.Pool(n_cores) 
logging.basicConfig(format=format, level=logging.INFO) 

Quindi, nulla verrà ereditato.

Altrimenti, come hai detto tu, a causa di os.fork(), le cose verranno ereditate/duplicate. In questo caso, le opzioni stanno riconfigurando la registrazione dopo aver creato il pool (vedere l'altra risposta) o altri suggerimenti/risposte.