Python: module de journalisation-globalement

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

j'ai

class customLogger(logging.Logger):
   ...

dans un fichier avec ses formateurs et d'autres choses. L'enregistreur fonctionne parfaitement sur son propre.

- je importer ce module dans mon main.py fichier et créer un objet comme ceci:

self.log = log.customLogger(arguments)

Mais évidemment je ne peux pas accéder à cet objet à partir d'autres parties de mon code. Est-ce que j'utilise une mauvaise approche? Est-il une meilleure façon de faire cette?

39
demandé sur cwoebker 2011-10-01 21:48:22

4 réponses

Utiliser logging.getLogger(name) pour créer un logger 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
88
répondu koehlma 2017-10-01 21:43:06

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 logging bibliothèque, qui vient avec la bibliothèque standard de Python.

contrairement à L'approche de L'OP (poster original), la bibliothèque sépare clairement l'interface de l'enregistreur et la configuration de l'enregistreur lui-même.

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

cela signifie Que vous devez créer une classe Logger personnalisée et configurer le logger à l'intérieur de cette classe en ajoutant n'importe quelle configuration ou n'importe quoi.

logging bibliothèque présente quatre composantes: enregistreurs,les gestionnaires,filtres et formateurs.

  • les bûcherons exposent interface que le code d'application utilise directement.
  • les gestionnaires envoient les enregistrements (créés par les bûcherons) à la destination appropriée.
  • les filtres fournissent une facilité plus fine pour déterminer quels enregistrements log à la sortie.
  • les formateurs spécifient la disposition des enregistrements log 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 logger de votre module pour enregistrer les événements à leur niveau spécifique.

une bonne convention à utiliser pour nommer les loggers est d'utiliser un logger au niveau du module, dans chaque module qui utilise la journalisation, nommé comme suit:

logger = logging.getLogger(__name__)

La variable spéciale __name__ renvoie au nom de votre module et ressemble à quelque chose comme project.package.module selon le code de votre application structure.

module.py (et de toute autre catégorie) 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 dans chaque module propagera n'importe quel événement à l'enregistreur parent qui en retour transmet les informations à son attaché gestionnaire! De la même manière que pour la structure paquet/module python, le logger parent est déterminé par l'espace de noms en utilisant des "noms de modules en pointillés". C'est pourquoi il est préférable d'initialiser l'enregistreur avec la __name__ variable (dans l'exemple ci-dessus nom correspond à la chaîne "projet.paquet.module").

il y a deux options pour configurer le logger globalement:

  • Instancier un enregistreur project.py le nom __package__ qui est égal à "projet" dans cet exemple et est donc le logger parent des loggers de tous les sous-modules. Il suffit d'ajouter un gestionnaire approprié et formateur à logger.

  • configurer un logger avec un handler et un formatter dans le script d'exécution (comme main.py) avec le nom du paquet le plus haut.

lors du développement d'une bibliothèque qui utilise la journalisation, vous devez prendre soin de documenter comment la bibliothèque utilise la journalisation - par exemple, les noms des loggers utilisés.

le script d'exécution, comme main.py par exemple, pourrait enfin ressembler à quelque chose comme 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 __name__ == '__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 de la plus faible sévérité qu'un logger va poignée mais pas nécessairement de sortie! Cela signifie simplement que le message est transmis au gestionnaire aussi longtemps que le niveau de gravité du message est supérieur (ou égal) à celui qui est défini. Mais l' gestionnaire responsable manutention le message log (par impression ou le stockage par exemple).

D'où le logging bibliothèque offre une approche structurée et modulaire qui doit juste être exploitée en fonction de ses besoins.

documentation de journalisation

26
répondu Dennis 2017-05-11 12:44:01

Créer une instance de customLogger dans votre module log et l'utiliser comme un singleton-il suffit d'utiliser l'instance importée, plutôt que la classe.

7
répondu Amber 2011-10-01 18:00:18

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 la période (".") peut être utilisé pour différentes classes / modules / fichiers / etc. Comme ainsi (en particulier le 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 __name__ == "__main__":
    # .....
    MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
3
répondu chown 2011-10-01 18:00:38