web-dev-qa-db-fra.com

Python: module de journalisation - globalement

Je me demandais comment implémenter un enregistreur global qui pourrait être utilisé partout avec vos propres paramètres:

J'ai actuellement une classe d'enregistreur personnalisée:

class customLogger(logging.Logger):
   ...

La classe est dans un fichier séparé avec quelques formateurs et autres trucs. L'enregistreur fonctionne parfaitement seul.

J'importe ce module dans mon fichier principal python et je crée un objet comme celui-ci:

self.log = logModule.customLogger(arguments)

Mais évidemment, je ne peux pas accéder à cet objet à partir d'autres parties de mon code. Suis-je en train d'utiliser une mauvaise approche? Y a-t-il une meilleure manière de faire cela?

49
cwoebker

Utilisez logging.getLogger(name) pour créer un enregistreur global nommé.

main.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

Sortie

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message
105
koehlma

Comme je n'ai pas trouvé de réponse satisfaisante, je voudrais développer un peu la réponse à la question afin de donner un aperçu du fonctionnement et des intentions de la bibliothèque logging, fournie avec le standard de Python. bibliothèque.

Contrairement à l'approche de l'OP (affiche originale), la bibliothèque sépare clairement l'interface avec l'enregistreur et la configuration de l'enregistreur lui-même.

La configuration des gestionnaires est la prérogative du développeur d'application qui utilise votre bibliothèque.

Cela signifie que vous devez not ​​créer une classe d'enregistreur personnalisée et configurer l'enregistreur à l'intérieur de cette classe en ajoutant une configuration ou autre.

La bibliothèque logging introduit quatre composants: loggers, handlers, filters, et formers.

  • Les enregistreurs exposent l'interface que le code d'application utilise directement.
  • Les gestionnaires envoient les enregistrements de journal (créés par les enregistreurs) à la destination appropriée.
  • Les filtres offrent une fonction plus fine pour déterminer les enregistrements de journaux à afficher.
  • Les formateurs spécifient la disposition des enregistrements de journal dans la sortie finale.

Une structure de projet commune ressemble à ceci:

Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...

Dans votre code (comme dans module.py ), vous vous référez à l'instance de l'enregistreur de votre module pour enregistrer les événements à leurs niveaux spécifiques.

Une bonne convention à utiliser pour nommer les enregistreurs consiste à utiliser un enregistreur de niveau module, dans chaque module qui utilise la journalisation, nommé comme suit:

logger = logging.getLogger(__name__)

La variable spéciale __name__ Fait référence au nom de votre module et ressemble à project.package.module En fonction de la structure de code de votre application.

module.py (et toute autre classe) pourrait essentiellement ressembler à ceci:

import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')

L'enregistreur de chaque module propage tout événement à l'enregistreur parent qui, en retour, transmet les informations à son attaché gestionnaire! De manière similaire à la structure de module/module python, l'enregistreur parent est déterminé par l'espace de noms à l'aide de "noms de module en pointillés". C'est pourquoi il est logique d'initialiser l'enregistreur avec le __name__ variable (dans l'exemple ci-dessus nom correspond à la chaîne "project.package.module").

Il existe deux options pour configurer l'enregistreur globalement:

  • Instanciez un enregistreur dans project.py avec le nom __package__ Qui est égal à "project" dans cet exemple et est donc l'enregistreur parent des enregistreurs de tous les sous-modules. Il est seulement nécessaire d'ajouter un gestionnaire et un formateur appropriés à this logger.

  • Configurez un enregistreur avec un gestionnaire et un formateur dans le script d'exécution (comme main.py ) avec le nom du package le plus haut.

Lors du développement d'une bibliothèque qui utilise la journalisation, vous devez prendre soin de documenter la façon dont la bibliothèque utilise la journalisation - par exemple, les noms des enregistreurs utilisés.

Le script d'exécution, comme main.py par exemple, pourrait finalement ressembler à ceci:

import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __== '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()

L'appel de méthode log.setLevel(...) spécifie le message de journal le moins grave qu'un enregistreur manipulera mais pas nécessairement en sortie! Cela signifie simplement que le message est transmis au gestionnaire tant que le niveau de gravité du message est supérieur (ou égal) à celui qui est défini. Mais le gestionnaire est responsable de manipulation le message du journal (en l'imprimant ou en le stockant par exemple).

La bibliothèque logging propose donc une approche structurée et modulaire qui ne demande qu'à être exploitée selon ses besoins.

Documentation de journalisation

41
Dennis

Créez une instance de customLogger dans votre module de journal et utilisez-la comme singleton - utilisez simplement l'instance importée, plutôt que la classe.

9
Amber

Vous pouvez simplement lui passer une chaîne avec une sous-chaîne commune avant la première période. Les parties de la chaîne séparées par le point (".") Peuvent être utilisées pour différentes classes/modules/fichiers/etc. Comme cela (en particulier la partie logger = logging.getLogger(loggerName)):

def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
    base = os.path.basename(__file__)
    loggerName = "%s.%s" % (base, name)
    logFileName = os.path.join(logdir, "%s.log" % loggerName)
    logger = logging.getLogger(loggerName)
    logger.setLevel(level)
    i = 0
    while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
        i += 1
        logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
    try:
        #fh = logging.FileHandler(logFileName)
        fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
    except IOError, exc:
        errOut = "Unable to create/open log file \"%s\"." % logFileName
        if exc.errno is 13: # Permission denied exception
            errOut = "ERROR ** Permission Denied ** - %s" % errOut
        Elif exc.errno is 2: # No such directory
            errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
        Elif exc.errno is 24: # Too many open files
            errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
        else:
            errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
        raise LogException(errOut)
    else:
        formatter = logging.Formatter(logformat)
        fh.setLevel(level)
        fh.setFormatter(formatter)
        logger.addHandler(fh)
    return logger

class MainThread:
    def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
        self.logdir = logdir
        logLevel = logging.DEBUG
        logPrefix = "MainThread_TEST" if self.test else "MainThread"
        try:
            self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
        except LogException, exc:
            sys.stderr.write("%s\n" % exc)
            sys.stderr.flush()
            os._exit(0)
        else:
            self.logger.debug("-------------------- MainThread created.  Starting __init__() --------------------")

    def run(self):
        self.logger.debug("Initializing ReportThreads..")
        for (group, cfg) in self.config.items():
            self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------     " % group)
            for k2, v2 in cfg.items():
                self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
            try:
                rt = ReportThread(self, group, cfg, self.logdir, self.test)
            except LogException, exc:
                sys.stderr.write("%s\n" % exc)
                sys.stderr.flush()
                self.logger.exception("Exception when creating ReportThread (%s)" % group)
                logging.shutdown()
                os._exit(1)
            else:
                self.threads.append(rt)
        self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
        for t in self.threads:
            t.Start()
        if not self.test:
            self.loop()


class ReportThread:
    def __init__(self, mainThread, name, config, logdir, test):
        self.mainThread = mainThread
        self.name = name
        logLevel = logging.DEBUG
        self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
        self.logger.info("init database...")
        self.initDB()
        # etc....

if __== "__main__":
    # .....
    MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
3
chown