2013-06-05 15 views
15

Ho un modulo log.py, utilizzato in almeno altri due moduli (e device.py).Registrazione di Python da più thread

Ha queste variabili globali:

fileLogger = logging.getLogger() 
fileLogger.setLevel(logging.DEBUG) 
consoleLogger = logging.getLogger() 
consoleLogger.setLevel(logging.DEBUG) 

file_logging_level_switch = { 
    'debug': fileLogger.debug, 
    'info':  fileLogger.info, 
    'warning': fileLogger.warning, 
    'error': fileLogger.error, 
    'critical': fileLogger.critical 
} 

console_logging_level_switch = { 
    'debug': consoleLogger.debug, 
    'info':  consoleLogger.info, 
    'warning': consoleLogger.warning, 
    'error': consoleLogger.error, 
    'critical': consoleLogger.critical 
} 

Ha due funzioni:

def LoggingInit(logPath, logFile, html=True): 
    global fileLogger 
    global consoleLogger 

    logFormatStr = "[%(asctime)s %(threadName)s, %(levelname)s] %(message)s" 
    consoleFormatStr = "[%(threadName)s, %(levelname)s] %(message)s" 

    if html: 
     logFormatStr = "<p>" + logFormatStr + "</p>" 

    # File Handler for log file 
    logFormatter = logging.Formatter(logFormatStr) 
    fileHandler = logging.FileHandler( 
     "{0}{1}.html".format(logPath, logFile)) 
    fileHandler.setFormatter(logFormatter) 
    fileLogger.addHandler(fileHandler) 

    # Stream Handler for stdout, stderr 
    consoleFormatter = logging.Formatter(consoleFormatStr) 
    consoleHandler = logging.StreamHandler() 
    consoleHandler.setFormatter(consoleFormatter) 
    consoleLogger.addHandler(consoleHandler) 

E:

def WriteLog(string, print_screen=True, remove_newlines=True, 
     level='debug'): 

    if remove_newlines: 
     string = string.replace('\r', '').replace('\n', ' ') 

    if print_screen: 
     console_logging_level_switch[level](string) 

    file_logging_level_switch[level](string) 

chiamo LoggingInit da server.py, che inizializza i taglialegna di file e console . Quindi chiamo WriteLog da tutte le parti, quindi più thread stanno accedendo a e a consoleLogger.

Ho bisogno di ulteriore protezione per il mio file di registro? La documentazione afferma che i blocchi dei thread sono gestiti dal gestore.

+0

Se non si inserisce il blocco quando si desidera scrivere, i record del registro potrebbero mescolare e creare un registro illeggibile. – AliBZ

+0

@AliBZ - quale registro registra? al livello di registrazione? non sono coperti dal servizio di registrazione Python? http://stackoverflow.com/questions/2973900/is-pythons-logging-module-thread-safe –

+0

quando si utilizza "fileLogger.info ('1 2 3 4')" in due thread diversi, il registro finale potrebbe essere un mix di loro, qualcosa come questo "1 2 1 2 3 3 4 4" – AliBZ

risposta

38

La buona notizia è che non è necessario fare nulla in più per la sicurezza delle filettature, e non è necessario nient'altro o qualcosa di quasi banale per l'arresto pulito. Vedrò i dettagli più tardi.

La cattiva notizia è che il tuo codice ha un problema serio anche prima di arrivare a quel punto: fileLogger e consoleLogger sono lo stesso oggetto. Da the documentation for getLogger():

ritorno un registratore con il nome specificato oppure, se non è specificato alcun nome, restituisce un logger che è il logger principale della gerarchia.

Quindi, che stai ricevendo il logger principale e la memorizzazione come fileLogger, e quindi che stai ricevendo il logger principale e la memorizzazione come consoleLogger. Quindi, in LoggingInit, si inizializza , quindi si reinizializza lo stesso oggetto con un nome diverso con valori diversi.

È possibile aggiungere più gestori allo stesso logger-e, dato che l'unico di inizializzazione effettivamente fare per ciascuno è addHandler, il codice tipo di lavoro come previsto, ma solo per caso. E solo una specie di. Otterrai due copie di ciascun messaggio in entrambi i registri se superi print_screen=True e otterrai copie nella console anche se superi print_screen=False.

In realtà non esiste alcuna ragione per le variabili globali; l'intero punto di è che è possibile chiamarlo ogni volta che ne avete bisogno e ottenere il logger di root globale, quindi non è necessario memorizzarlo ovunque.


Un altro problema minore è che non stai sfuggendo al testo che inserisci in HTML. Ad un certo punto tenterai di registrare la stringa "a < b" e finire nei guai.

Meno seriamente, una sequenza di tag <p> che non si trova all'interno di <body> all'interno di <html> non è un documento HTML valido. Ma molti spettatori si prenderanno cura di ciò automaticamente, oppure puoi post-elaborare i tuoi log banalmente prima di visualizzarli.Ma se vuoi davvero che questo sia corretto, devi creare una sottoclasse di FileHandler e avere il tuo __init__ aggiungere un'intestazione se hai un file vuoto e rimuovere un piè di pagina, se presente, quindi aggiungi il piè di pagina al tuo close.


Tornando alla tua domanda effettiva:

non avete bisogno di alcun blocco aggiuntivo. Se un gestore implementa correttamente createLock, acquire e release (e viene chiamato su una piattaforma con thread), la macchina di registrazione si accerterà automaticamente di acquisire il blocco quando necessario per assicurarsi che ogni messaggio sia registrato in modo atomico.

Per quanto ne so, la documentazione non lo fa direttamente dicono che StreamHandler e FileHandler implementare questi metodi, lo fa con forza implica che (the text you mentioned in the question dice: "Il modulo di registrazione è destinato ad essere thread-safe, senza alcun lavoro speciale che deve essere fatto dai suoi clienti ", ecc.). Inoltre, puoi esaminare il codice sorgente per la tua implementazione (ad es., CPython 3.3) e verificare che entrambi ereditino i metodi correttamente implementati da logging.Handler.


Allo stesso modo, se un gestore implementa correttamente flush e close, il macchinario di registrazione farà in modo che sia finalizzato in modo corretto durante l'arresto normale.

Qui, la documentazione spiega cosa StreamHandler.flush(), FileHandler.flush() e FileHandler.close(). Sono per lo più come ci si aspetterebbe, ad eccezione del fatto che lo StreamHandler.close() è un no-op, il che significa che è possibile che i messaggi di log finali della console si perdano. Dalla documentazione:

Si noti che il metodo close() è ereditato dalla Handler e così fa nessuna uscita, quindi un esplicito flush() chiamata può essere necessaria, a volte.

Se questo è importante per voi, e si vuole risolvere il problema, è necessario fare qualcosa di simile:

class ClosingStreamHandler(logging.StreamHandler): 
    def close(self): 
     self.flush() 
     super().close() 

e quindi utilizzare ClosingStreamHandler() invece di StreamHandler().

FileHandler non presenta questo problema.


Il modo normale di inviare i log a due posti è usare solo il logger principale con due gestori, ognuno con il proprio formattatore.

Inoltre, anche se si desiderano due logger, non sono necessarie le mappe console_logging_level_switch e file_logging_level_switch separate; chiamare Logger.debug(msg) equivale esattamente a chiamare Logger.log(DEBUG, msg). Avrai comunque bisogno di un modo per mappare i nomi dei livelli personalizzati debug, ecc. Con i nomi standard DEBUG, ecc., Ma puoi semplicemente eseguire una ricerca, invece di farlo una volta per logger (in più, se i tuoi nomi sono solo nomi standard con cast diversi, puoi imbrogliare).

Questo è tutto descritto abbastanza bene nella sezione `Multiple handlers and formatters e il resto del libro di ricette di registrazione.

L'unico problema con il modo standard di eseguire questa operazione è che non è possibile disattivare facilmente la registrazione della console in base al messaggio per messaggio. Questo perché non è una cosa normale da fare. In genere, è sufficiente accedere per livelli e impostare il livello di registro più in alto nel registro file.

Tuttavia, se si desidera un maggiore controllo, è possibile utilizzare i filtri. Ad esempio, inserisci il tuo filtro FileHandler che accetta tutto e il tuo ConsoleHandler un filtro che richiede qualcosa che inizia con console, quindi utilizza il filtro 'console' if print_screen else ''. Ciò riduce lo WriteLog a quasi un rivestimento lineare.

Hai ancora bisogno di due linee extra per rimuovere le nuove linee, ma puoi anche fare nel filtro o, se lo desideri, tramite un adattatore. (Anche in questo caso, guarda il libro di cucina.) E poi WriteLog in realtà è un unico rivestimento.

+0

Grazie per i suggerimenti aggiuntivi. Questo significa che tutto ciò che devo fare è specificare i nomi in 'getLogger()' e funzionerà come previsto? – nckturner

+0

e, @abarnert se voglio continuare a loggare in due posti diversi (specificando i nomi in 'getLogger()', avrò ancora bisogno dei globali, corretto? – nckturner

+0

@Raskol: Beh, dipende da cosa significa "inteso" ma probabilmente non è quello che vuoi veramente.L'idea alla base di 'getLogger' è che dovresti usarla per una sorta di struttura gerarchica dei log; si noti che la documentazione di "Logger Objects" parla della "costruzione raccomandata' logging.getLogger (__ name __) 'per far corrispondere la gerarchia dei moduli alla gerarchia dei moduli. Si può sovvertire ciò per avere due logger affiancati, ma è una cosa strana – abarnert