Utilizzo della registrazione in più moduli


257

Ho un piccolo progetto Python che ha la seguente struttura:

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Ho intenzione di utilizzare il modulo di registrazione predefinito per stampare i messaggi su stdout e un file di registro. Per utilizzare il modulo di registrazione, è necessaria un'inizializzazione:

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

Al momento, eseguo questa inizializzazione in ogni modulo prima di iniziare a registrare i messaggi. È possibile eseguire questa inizializzazione solo una volta in una posizione tale da riutilizzare le stesse impostazioni registrandosi in tutto il progetto?


3
In risposta al tuo commento sulla mia risposta: non è necessario chiamare fileConfigtutti i moduli che effettuano la registrazione, a meno che non si abbia la if __name__ == '__main__'logica in tutti. La risposta di prost non è una buona pratica se il pacchetto è una libreria, anche se potrebbe funzionare per te - non si dovrebbe configurare la registrazione nei pacchetti della libreria, se non quello di aggiungere un NullHandler.
Vinay Sajip,

1
prost ha implicato che dobbiamo chiamare gli stmts di importazione e logger in ogni modulo e chiamare solo lo stect fileconfig nel modulo principale. non è simile a quello che stai dicendo?
Quest Monger,

6
prost sta dicendo che dovresti inserire il codice di configurazione della registrazione package/__init__.py. Normalmente non è il posto in cui hai inserito il if __name__ == '__main__'codice. Inoltre, l'esempio di prost sembra che chiamerà il codice di configurazione incondizionatamente all'importazione, il che non mi sembra giusto. In genere, la registrazione del codice di configurazione deve essere eseguita in un unico punto e non dovrebbe verificarsi come effetto collaterale dell'importazione, tranne quando si importa __main__.
Vinay Sajip,

hai ragione, ho completamente perso la riga '# package / __ init__.py' nel suo esempio di codice. grazie per averlo sottolineato e la tua pazienza.
Quest Monger

1
Quindi cosa succede se ne hai più if __name__ == '__main__'? (Non è menzionato esplicitamente in questione se questo è il caso)
kon psych,

Risposte:


293

La migliore pratica è, in ogni modulo, avere un logger definito in questo modo:

import logging
logger = logging.getLogger(__name__)

vicino alla parte superiore del modulo e quindi in un altro codice del modulo, ad es

logger.debug('My message with %s', 'variable data')

Se è necessario suddividere l'attività di registrazione all'interno di un modulo, utilizzare ad es

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

e accedere a loggerAe loggerB, se del caso.

Nel tuo programma o programmi principali, ad esempio:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

o

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Vedere qui per la registrazione da più moduli e qui per la configurazione della registrazione per il codice che verrà utilizzato come modulo di libreria da altro codice.

Aggiornamento: quando chiami fileConfig(), potresti voler specificare disable_existing_loggers=Falsese stai usando Python 2.6 o successivo (vedi i documenti per maggiori informazioni). Il valore predefinito è Trueper la compatibilità con le versioni precedenti, che causa la disabilitazione di tutti i logger esistenti a fileConfig()meno che loro o il loro antenato non siano esplicitamente nominati nella configurazione. Con il valore impostato su False, i logger esistenti vengono lasciati soli. Se si utilizza Python 2.7 / Python 3.2 o versioni successive, è consigliabile prendere in considerazione l' dictConfig()API, che è migliore rispetto fileConfig()a quando offre un maggiore controllo sulla configurazione.


21
se guardi il mio esempio, sto già facendo ciò che suggerisci sopra. la mia domanda era come centralizzare questa inizializzazione della registrazione in modo tale che non devo ripetere quelle 3 affermazioni. inoltre, nel tuo esempio hai perso lo stmt 'logging.config.fileConfig (' logging.conf ')'. questo stmt è in realtà la causa principale della mia preoccupazione. vedi, se avessi avviato il logger in ogni modulo, avrei dovuto digitare questo stmt in ogni modulo. ciò significherebbe tracciare il percorso del file conf in ogni modulo, che non mi sembra una buona pratica (immagina il caos quando cambi i percorsi del modulo / pacchetto).
Quest Monger,

4
Se si chiama fileConfig dopo aver creato il logger, sia nello stesso che in un altro modulo (ad es. Quando si crea il logger nella parte superiore del file) non funziona. La configurazione della registrazione si applica solo ai logger creati successivamente. Quindi questo approccio non funziona o non è un'opzione praticabile per più moduli. @Quest Monger: puoi sempre creare un altro file che contiene la posizione del file di configurazione ..;)
Vincent Ketelaars,

2
@Oxidator: non necessariamente - vedi il disable_existing_loggersflag che è Truedi default ma può essere impostato su False.
Vinay Sajip,

1
@Vinay Sajip, grazie. Hai consigli per logger che funzionano in moduli ma anche fuori dalle classi? Poiché le importazioni vengono eseguite prima che venga chiamata la funzione principale, tali registri saranno già stati registrati. Immagino che impostare il tuo logger prima che tutte le importazioni nel modulo principale sia l'unico modo? Questo logger potrebbe quindi essere sovrascritto in main, se lo desideri.
Vincent Ketelaars,

1
Se desidero che tutti i logger specifici del mio modulo abbiano un livello di registrazione diverso da quello predefinito ATTENZIONE, dovrò effettuare tale impostazione su ciascun modulo? Dì, voglio che tutti i miei moduli siano registrati su INFO.
Raj,

128

In realtà ogni logger è figlio del logger dei pacchetti del genitore (ovvero package.subpackage.moduleeredita la configurazione da package.subpackage), quindi tutto ciò che devi fare è solo configurare il logger di root. Ciò può essere ottenuto da logging.config.fileConfig(la tua configurazione per i logger) o logging.basicConfig(imposta il logger di root) . Setup logging nel tuo modulo di accesso ( __main__.pyo qualunque cosa tu voglia eseguire, per esempio main_script.py. __init__.pyFunziona anche)

utilizzando basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

utilizzando fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

e quindi creare ogni logger usando:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Per ulteriori informazioni, consultare Tutorial per la registrazione avanzata .


15
questa è di gran lunga la soluzione più semplice al problema, per non parlare del fatto che espone e sfrutta la relazione genitore-figlio tra i moduli, qualcosa di cui io, in quanto noob, non ero a conoscenza. danke.
Quest Monger,

hai ragione. e come ha sottolineato vinay nel suo post, la tua soluzione è giusta fintanto che non è nel modulo init .py. la tua soluzione ha funzionato quando l'ho applicata al modulo principale (punto di entrata).
Quest Monger

2
in realtà risposta molto più pertinente poiché la domanda riguarda moduli separati.
Jan Sila,

1
Domanda stupida forse: se non c'è un logger __main__.py(es. Se voglio usare il modulo in uno script che non ha logger) farà logging.getLogger(__name__)comunque qualche tipo di logging nel modulo o genererà un'eccezione?
Bill,

1
Finalmente. Avevo un logger funzionante, ma non funzionava in Windows per Parallel funziona con joblib. Immagino che questa sia una modifica manuale al sistema - qualcos'altro non va in Parallel. Ma sicuramente funziona! Grazie
B Furtado,

17

Lo faccio sempre come di seguito.

Usa un singolo file Python per configurare il mio registro come modello singleton che si chiama ' log_conf.py'

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

In un altro modulo, basta importare la configurazione.

from log_conf import Logger

Logger.logr.info("Hello World")

Questo è un modello singleton per registrare, in modo semplice ed efficiente.


1
grazie per il dettaglio del modello singleton. stavo programmando di implementarlo, ma poi la soluzione @prost è molto più semplice e si adatta perfettamente alle mie esigenze. tuttavia vedo che la tua soluzione è utile se i progetti più grandi hanno più punti di ingresso (diversi da quelli principali). danke.
Quest Monger,

46
Questo è inutile. Il logger di root è già un singleton. Basta usare logging.info invece di Logger.logr.info.
Pod

9

Molte di queste risposte suggeriscono che nella parte superiore di un modulo lo fai

import logging
logger = logging.getLogger(__name__)

Comprendo che questa è considerata una cattiva pratica . Il motivo è che la configurazione del file disabiliterà tutti i logger esistenti per impostazione predefinita. Per esempio

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

E nel tuo modulo principale:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Ora il registro specificato in logging.ini sarà vuoto, poiché il logger esistente è stato disabilitato dalla chiamata fileconfig.

Mentre è certamente possibile aggirare questo (disable_existing_Loggers = False), realisticamente molti client della tua libreria non conosceranno questo comportamento e non riceveranno i tuoi log. Semplifica i tuoi clienti chiamando sempre logging.getLogger localmente. Suggerimento per il cappello: ho imparato questo comportamento dal sito Web di Victor Lin .

Pertanto, è buona norma chiamare sempre logging.getLogger localmente. Per esempio

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Inoltre, se si utilizza fileconfig nel proprio main, impostare disable_existing_loggers = False, nel caso in cui i progettisti della libreria utilizzino le istanze del logger a livello di modulo.


Non puoi correre logging.config.fileConfig('logging.ini')prima import my_module? Come suggerito in questa risposta .
lucid_dreamer,

Non ne sono sicuro, ma sarebbe sicuramente considerata una cattiva pratica mescolare le importazioni e il codice eseguibile in quel modo. Inoltre, non vuoi che i tuoi clienti debbano verificare se devono configurare la registrazione prima di importarli, soprattutto quando esiste un'alternativa banale! Immagina se una libreria ampiamente usata come le richieste lo avesse fatto ....!
phil_20686,

"Non ne sono sicuro, ma sarebbe sicuramente considerato una cattiva pratica mescolare le importazioni e il codice eseguibile in quel modo." - perché?
lucid_dreamer,

Non sono troppo chiaro sul perché sia ​​negativo. E non capisco perfettamente il tuo esempio. Puoi pubblicare la tua configurazione per questo esempio e mostrare un po 'di utilizzo?
lucid_dreamer,

1
Sembri contraddire i documenti ufficiali : "Una buona convenzione da usare quando si nominano i logger è quella di utilizzare un logger a livello di modulo, in ogni modulo che utilizza la registrazione, chiamato come segue: logger = logging.getLogger(__name__)"
iron9

9

Per me un modo semplice di utilizzare un'istanza di registrazione della libreria in più moduli era la seguente soluzione:

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Altri file

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")

7

Lancio in un'altra soluzione.

In init .py del mio modulo ho qualcosa del tipo:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Quindi in ogni modulo ho bisogno di un logger, faccio:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Quando mancano i registri, è possibile differenziare la loro origine dal modulo da cui provengono.


Cosa significa "init principale del mio modulo"? E "Quindi in ogni classe ho bisogno di un logger, faccio:"? Potete fornire un esempio chiamato_module.py e un esempio del suo utilizzo come importazione dal modulo caller_module.py? Vedi questa risposta per l'ispirazione del formato che sto chiedendo. Non cercare di essere condiscendente. Sto cercando di capire la tua risposta e so che lo farei se la scrivessi in quel modo.
lucid_dreamer,

1
@lucid_dreamer ho chiarito.
Tommy

4

Potresti anche inventarti qualcosa del genere!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Ora è possibile utilizzare più logger nello stesso modulo e nell'intero progetto se quanto sopra è definito in un modulo separato e importato in altri moduli in cui è richiesta la registrazione.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")

4

@ La soluzione di Yarkee sembrava migliore. Vorrei aggiungere qualcosa in più -

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

Quindi LoggerManager può essere collegabile a tutta l'applicazione. Spero abbia senso e valore.


11
Il modulo di registrazione si occupa già dei singoli. logging.getLogger ("Hello") otterrà lo stesso logger su tutti i tuoi moduli.
Pod

2

Ci sono diverse risposte ho finito con una soluzione simile ma diversa che ha senso per me, forse avrà senso anche per te. Il mio obiettivo principale era quello di essere in grado di passare i registri ai gestori in base al loro livello (registri di livello di debug alla console, avvisi e soprattutto ai file):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

creato un bel file util chiamato logger.py:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

flask.app è un valore hardcoded in flask. il logger dell'applicazione inizia sempre con flask.app come nome del modulo.

ora, in ogni modulo, sono in grado di usarlo nella seguente modalità:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Ciò creerà un nuovo registro per "app.flask.MODULE_NAME" con il minimo sforzo.


2

La migliore pratica sarebbe quella di creare un modulo separatamente che ha un solo metodo il cui compito è quello di dare un gestore logger al metodo chiamante. Salva questo file come m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Ora chiama il metodo getlogger () ogni volta che è necessario il gestore del logger.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

1
Questo è utile se non hai parametri aggiuntivi. Ma se, diciamo, hai --debugun'opzione nell'app e vuoi impostare il livello di registrazione in tutti i logger nella tua app in base a questo parametro ...
The Godfather

@TheGodfather Sì, è difficile ottenere questa metodologia. Ciò che possiamo fare in questa situazione è quello di creare una classe per la quale prenderebbe il formattatore come parametro al momento della creazione dell'oggetto e avrebbe la stessa funzione di restituire il gestore logger. Quali sono le tue opinioni al riguardo?
Mousam Singh,

Sì, ho fatto qualcosa di simile, fatto get_logger(level=logging.INFO)per restituire una sorta di singleton, quindi quando ha chiamato la prima volta dall'app principale, inizializza il logger e i gestori con il livello corretto e quindi restituisce lo stesso loggeroggetto a tutti gli altri metodi.
Il padrino,

0

Nuovo su Python, quindi non so se questo sia consigliabile, ma funziona benissimo per non riscrivere il boilerplate.

Il tuo progetto deve avere un init .py in modo che possa essere caricato come modulo

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1)il suggerimento viene da qui

Quindi per usare il tuo logger in qualsiasi altro file:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Avvertenze:

  1. È necessario eseguire i file come moduli, altrimenti import [your module]non funzionerà:
    • python -m [your module name].[your filename without .py]
  2. Il nome del logger per il punto di ingresso del programma sarà __main__, ma qualsiasi soluzione che utilizza __name__avrà quel problema.
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.