messaggi di log che compaiono due volte con Python Logging


100

Sto usando la registrazione di Python e, per qualche motivo, tutti i miei messaggi vengono visualizzati due volte.

Ho un modulo per configurare la registrazione:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Successivamente, chiamo questo metodo per configurare la registrazione:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

E poi, ad esempio, il modulo buy_ham, chiamerei:

self.logger.info('Successfully able to write to %s' % path)

E per qualche motivo, tutti i messaggi vengono visualizzati due volte. Ho commentato uno dei gestori di flusso, sempre la stessa cosa. Un po 'strano, non sono sicuro del motivo per cui sta accadendo ... lol. Supponendo che mi sia perso qualcosa di ovvio.

Salute, Victor


1
Sei sicuro che configure_logging()non venga chiamato due volte (es. Anche dal costruttore)? È stata creata solo un'istanza di Boy ()?
Jacek Konieczny

1
L'utilizzo self.logger.handlers = [ch]invece risolverà questo problema, anche se sarebbe meglio assicurarsi di non eseguire questo codice due volte, ad esempio utilizzando if not self.loggerall'inizio.
Ninjakannon

Risposte:


134

Stai chiamando configure_loggingdue volte (forse nel __init__metodo di Boy): getLoggerrestituirà lo stesso oggetto, ma addHandlernon controlla se un gestore simile è già stato aggiunto al logger.

Prova a tracciare le chiamate a quel metodo ed eliminarne uno. Oppure imposta un flag logging_initializedinizializzato a Falsenel __init__metodo di Boye cambia configure_loggingper non fare nulla se logging_initializedè True, e per impostarlo Truedopo aver inizializzato il logger.

Se il tuo programma crea diverse Boyistanze, dovrai cambiare il modo in cui fai le cose con una configure_loggingfunzione globale che aggiunge i gestori e il Boy.configure_loggingmetodo inizializza solo l' self.loggerattributo.

Un altro modo per risolvere questo problema è controllare l'attributo handlers del tuo logger:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
Sì, avevi ragione - sciocco io. L'ho chiamato in init , così come esplicitamente altrove. Lol. Grazie =).
victorhooi

Grazie. La tua soluzione mi ha salvato oggi.
ForeverLearner

1
Nel mio caso, sono apparsi 6 volte. Lo avevo sospettato perché ho dichiarato lo stesso tipo di logger in 6 classi oop
answerSeeker

5
Vorrei condividere qui la mia esperienza: per un'applicazione Flask che ho sviluppato, i messaggi di log apparivano PIÙ DI DUE VOLTE. Direi che si stavano incrementando sul file di log, in quanto, al momento del caricamento dell'applicazione e dei moduli, la loggervariabile utilizzata non era quella istanziata da una delle mie classi, ma la loggervariabile presente sulla cache di Python3 e il gestore è stato aggiunto ogni 60 secondi da un AppScheduler che ho configurato. Quindi, questo if not logger.handlersè un modo abbastanza intelligente per evitare questo tipo di fenomeno. Grazie per la soluzione, compagno :)!
ivanleoncz

2
Sto riscontrando questo problema nella mia app Flask. Questa soluzione ha risolto il problema per i messaggi di registro generati nell'app flask principale, ma la mia app cals funziona in un modulo libreria e quei messaggi da quella libreria vengono ancora registrati più volte. Non so come rimediare.
Cas

24

Se stai riscontrando questo problema e non stai aggiungendo il gestore due volte, vedi la risposta di abarnert qui

Dai documenti :

Nota: se colleghi un gestore a un logger e uno o più dei suoi antenati, potrebbe emettere lo stesso record più volte. In generale, non dovresti aver bisogno di collegare un gestore a più di un logger - se lo colleghi semplicemente al logger appropriato che è il più alto nella gerarchia del logger, allora vedrà tutti gli eventi registrati da tutti i logger discendenti, a condizione che la loro propagazione l'impostazione viene lasciata impostata su True. Uno scenario comune è collegare i gestori solo al logger di root e lasciare che la propagazione si occupi del resto.

Quindi, se vuoi un gestore personalizzato su "test" e non vuoi che i suoi messaggi vadano anche al gestore root, la risposta è semplice: disattiva il flag di propagazione:

logger.propagate = False


1
Questa è la migliore risposta. Non si adattava allo scopo del poster (errore logico nella codifica) ma la maggior parte delle volte dovrebbe essere così.
Artem

Bravo. QUESTA è la vera causa dei duplicati (per i casi più generali).
Mr. Duhart

Questo era anche il problema con il mio codice. Molte grazie.
duro

La migliore risposta in assoluto. Grazie!
Foivos Ts

8

Il gestore viene aggiunto ogni volta che chiami dall'esterno. Prova a rimuovere il gestore dopo aver terminato il lavoro:

self.logger.removeHandler(ch)

1
Ho usato logger.handlers.pop() in Python 2.7, fa il trucco
radtek

6

Sono un principiante di Python, ma questo sembrava funzionare per me (Python 2.7)

while logger.handlers:
     logger.handlers.pop()

4

Nel mio caso dovrei impostare logger.propagate = Falseper evitare la doppia stampa.

Nel codice sottostante, se rimuovi logger.propagate = False, vedrai una doppia stampa.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

Questo è il problema che ho. Grazie
q0987

Bella risposta; l'aggiunta logger.propagate = Falseera la soluzione per impedire il doppio accesso in un'applicazione Flask ospitata da Waitress, quando si accedeva app.loggerall'istanza di Flask .
bluebinary

1

Una chiamata alle logging.debug()chiamate logging.basicConfig()se non sono installati gestori root. Ciò stava accadendo per me in un framework di test in cui non potevo controllare l'ordine di attivazione dei test case. Il mio codice di inizializzazione stava installando il secondo. L'impostazione predefinita utilizza logging.BASIC_FORMAT che non volevo.


Penso che questo sia quello che sta succedendo per me. Come si impedisce la creazione automatica di logger della console?
Robert,

@Robert si tratta di assicurarsi di essere inizializzati con il logger che si desidera, prima della prima chiamata di log. I framework di test possono oscurare questo aspetto, ma dovrebbe esserci un modo per farlo. Inoltre, se stai elaborando multiplo, devi fare lo stesso con ogni processo.
JimB

1

Sembra che se si invia qualcosa al logger (accidentalmente) e poi lo si configura, è troppo tardi. Ad esempio, nel mio codice avevo

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

Otterrei qualcosa del tipo (ignorando le opzioni di formato)

look out
hello
hello

e tutto è stato scritto due volte su stdout. Credo che ciò sia dovuto al fatto che la prima chiamata a logging.warningcrea automaticamente un nuovo gestore, quindi ho aggiunto esplicitamente un altro gestore. Il problema è andato via quando ho rimosso la prima logging.warningchiamata accidentale .


0

Stavo ottenendo una strana situazione in cui i registri della console erano raddoppiati ma i miei registri dei file non lo erano. Dopo un sacco di scavi ho capito.

Tieni presente che i pacchetti di terze parti possono registrare i logger. Questo è qualcosa a cui prestare attenzione (e in alcuni casi non può essere prevenuto). In molti casi il codice di terze parti controlla se sono presenti root esistenti gestori di logger ; e se non c'è, registrano un nuovo gestore della console.

La mia soluzione a questo era registrare il mio logger della console a livello di root:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
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.