Duplica l'output del registro quando si utilizza il modulo di registrazione Python


105

Sto usando Python logger. Quello che segue è il mio codice:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Il problema che ho è che ottengo più voci nel file di registro per ogni logger.infochiamata. Come posso risolvere questo?


Per me va bene. Python 3.2 e Windows XP.
Zuljin

2
Sei sicuro di non creare più istanze di logger?
Gandi

Sì. in un file diverso sto prendendo una nuova istanza come abbiamo fatto nei progetti Java. Per favore specificami se questo sta creando problemi o meno.
user865438

Risposte:


94

Il logging.getLogger()è già un Singleton. ( Documentazione )

Il problema è che ogni volta che chiami myLogger(), aggiunge un altro gestore all'istanza, il che causa i log duplicati.

Forse qualcosa del genere?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
Penso che dovresti avere loggers.update (dict ((name, logger))).
acrofobia

perchè loggers.update(dict(name=logger))? non è più loggers[name] = loggersemplice?
Ryan J McCall

@ RyanJMcCall A quel tempo era la convenzione di codifica che usavo. Ma dopo aver rivisto il codice così com'è, vedo che è rotto. loggers.update(dict(name=logger))creerà un dizionario con una singola chiave chiamata namee aggiornerà continuamente quella stessa chiave. Sono sorpreso che nessuno non l'abbia menzionato prima perché questo codice è abbastanza rotto :) Apporterà le modifiche richieste.
Werner Smit

Vedo che @acrophobia stava sfuggendo a questo secoli fa. Grazie.
Werner Smit

il loggersdizionario globale non è ridondante con logging.getLogger? dal momento che vuoi davvero evitare di aggiungere gestori extra, sembra che preferiresti le risposte di seguito che controllano direttamente i gestori
mway

60

A partire da Python 3.2 puoi semplicemente controllare se i gestori sono già presenti e, in tal caso, cancellarli prima di aggiungere nuovi gestori. Questo è abbastanza comodo durante il debug e il codice include l'inizializzazione del logger

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

Buona risposta, grazie :))
Gavriel Cohen

2
Nota che hasHandlers () restituirà true in pytest dove un gestore è stato aggiunto al logger di root, anche se i tuoi gestori locali / personalizzati non sono ancora stati aggiunti. Il len (logger.handlers) (come da risposta di Guillaume) restituirà 0 in questo caso, quindi potrebbe essere un'opzione migliore.
Concedi il

Questa è la vera soluzione che stavo cercando.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

ha fatto il trucco per me

utilizzando python 2.7


1
Funziona anche quando il modulo viene ricaricato (il che non è il caso delle altre risposte)
yco

3
Grazie per il suggerimento, BTW per verificare se un elenco è vuoto o meno non è necessario utilizzare l'operatore "len" che puoi utilizzare direttamente se my_list: ..
rkachach

26

Ho già usato loggercome Singleton e controllato if not len(logger.handlers), ma ho ancora i duplicati : era l'output formattato, seguito dal non formattato.

Soluzione nel mio caso: logger.propagate = False

Crediti a questa risposta e ai documenti .


1
Avevo capito che la registrazione raddoppiata proveniva da RootLogger e dal mio StreamHandler, ma non potevo risolvere il problema (mantenendo il mio formatter sullo StreamHandler) fino a quando non lo facevo.
Xander Yz,

10

Stai chiamando Logger.myLogger()più di una volta. Conservare l'istanza logger ritorna da qualche parte e riutilizzare quella .

Inoltre, tieni presente che se accedi prima che venga aggiunto un gestore, StreamHandler(sys.stderr)verrà creato un valore predefinito .


In realtà sto cercando di accedere all'istanza del logger come usiamo in java, ma non so se sia necessario creare un'istanza solo una volta per un intero progetto o meno.
user865438

1
@ user865483: solo una volta. Tutti i logger della libreria standard sono singleton.
Matt Joiner

5

Questa è un'aggiunta alla risposta di @ rm957377 ma con una spiegazione del motivo per cui sta accadendo . Quando esegui una funzione lambda in AWS, la chiamano da un'istanza di wrapping che rimane attiva per più chiamate. Significa che se chiami addHandler()all'interno del codice della tua funzione, continuerà ad aggiungere gestori duplicati al singleton di registrazione ogni volta che la funzione viene eseguita. Il singleton di registrazione persiste attraverso più chiamate della tua funzione lambda.

Per risolvere questo problema, puoi cancellare i tuoi gestori prima di impostarli tramite:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

In qualche modo nel mio caso i gestori del logger vengono aggiunti all'evento su .info()chiamata che non capisco.
Evgeny

4

Il tuo logger dovrebbe funzionare come singleton. Non dovresti crearlo più di una volta. Ecco un esempio di come potrebbe apparire:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

poi di nuovo se ho intenzione di prendere l'istanza diversa in un file diverso. Supponiamo che nel file 1 s = Logger () m = s.myLogger () e nel file 2 s = Logger () funzioni o meno m2 = s.myLogger ()
user865438

Ancora sto ottenendo una copia dello stesso registro più volte. Ho un dubbio qui se all'interno del log del thread ne stampi più di uno o meno, per favore aiutami in questo.
user865438

1
@ user865438, non dobbiamo preoccuparci di rendere l'implementazione un singleton (lo è già). Per accedere ai sottomoduli, segui il link ufficiale di Logging Cookbook . Fondamentalmente è necessario seguire la gerarchia dei nomi mentre si nominano i logger e si prende cura del resto.
Narayan

4

L'implementazione di logger è già un singleton.

Più chiamate a logging.getLogger ('someLogger') restituiscono un riferimento allo stesso oggetto logger. Ciò è vero non solo all'interno dello stesso modulo, ma anche tra i moduli fintanto che si trova nello stesso processo dell'interprete Python. È vero per i riferimenti allo stesso oggetto; inoltre, il codice dell'applicazione può definire e configurare un logger genitore in un modulo e creare (ma non configurare) un logger figlio in un modulo separato, e tutte le chiamate logger al figlio passeranno al genitore. Ecco un modulo principale

Sorgente: utilizzo della registrazione in più moduli

Quindi il modo in cui dovresti utilizzarlo è:

Supponiamo di aver creato e configurato un logger chiamato 'main_logger' nel modulo principale (che semplicemente configura il logger, non restituisce nulla).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Ora in un sottomodulo, se creiamo un child logger seguendo la gerarchia dei nomi 'main_logger.sub_module_logger' , non abbiamo bisogno di configurarlo nel sottomodulo. È sufficiente la semplice creazione del logger seguendo la gerarchia dei nomi.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

E non aggiungerà neanche un gestore duplicato.

Vedi questa domanda per una risposta un po 'più dettagliata.


1
la ridefinizione dei gestori dopo getLogger sembra funzionare per me: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

L'output del logger doppio (o triplo o ... in base al numero di ricariche) può verificarsi anche quando ricarichi il tuo modulo tramite importlib.reload(per lo stesso motivo spiegato nella risposta accettata). Sto aggiungendo questa risposta solo per un riferimento futuro poiché mi ci è voluto un po 'per capire perché il mio output è dupli (triplo) cato.


1

Una semplice soluzione è come

logger.handlers[:] = [handler]

In questo modo eviti di aggiungere un nuovo gestore alla lista sottostante "gestori".


1

In conclusione, per la maggior parte dei casi, quando ciò accade, è sufficiente chiamare logger.getLogger () solo una volta per modulo. Se hai più classi come ho fatto io, potrei chiamarlo così:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Entrambi avranno quindi il proprio nome completo del pacchetto e il metodo in cui sono stati registrati.


0

Sei in grado di ottenere l'elenco di tutti i gestori per il particolare logger, quindi puoi fare qualcosa del genere

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

Nell'esempio sopra, controlliamo se il gestore di un file specificato è già agganciato al logger, ma avere accesso all'elenco di tutti i gestori ti dà la possibilità di decidere su quali criteri aggiungere o meno un altro gestore.


0

Oggi ho avuto questo problema. Poiché le mie funzioni erano @staticmethod, i suggerimenti precedenti sono stati risolti con random ().

Assomigliare a qualcosa di simile:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

Aggiungere una spiegazione per rendere questa risposta più valida per un uso a lungo termine.
Aminah Nuraini
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.