web-dev-qa-db-fra.com

Connexion Python à plusieurs gestionnaires, à différents niveaux de journalisation?

Gens,

Je me gratte la tête sur une configuration de journalisation en python que je ne peux pas bien comprendre.

Disons que j'ai le paquet suivant installé:

mypackage/
   data/mypackage.logging.conf
   module1.py
   module2.py
   scripts/main.py

Comme le script peut être utilisé de manière interactive ou exécuté à partir d'une crontab, j'ai les exigences suivantes:

  1. aucune déclaration d'impression, tout passe par la journalisation;

  2. connectez-vous en utilisant une timedRotatingFileHandler, toujours au niveau DEBUG;

  3. en utilisant un mailinglogger.SummarisingLogger, toujours au niveau INFO;

  4. connectez-vous à la console, avec le niveau défini par défaut sur INFO ou remplacé par une option de ligne de commande.

Le problème, c'est que je peux changer le niveau de journalisation via la ligne de commande et que le niveau de journalisation de la console est modifié en conséquence, mais les autres gestionnaires sont également modifiés, ce que je ne souhaite pas ...: - /

Dans un fichier de configuration de la journalisation, je ne suis pas sûr de comprendre la priorité entre le niveau du consignateur racine, le niveau des autres enregistreurs et les paramètres de niveau des gestionnaires.

Voici un exemple de code. Tous les indices seront appréciés :-)

# mypackage/data/mypackage.logging.conf
[loggers]
root,mypackage

[handlers]
keys=consoleHandler,timedRotatingFileHandler,summarisingHandler

[formatters]
keys=simpleFormatter,consoleFormatter,mypackageFormatter

[logger_root]
#level=INFO
handlers=consoleHandler

[logger_mypackage]
#level=INFO
handlers=timedRotatingFileHandler,summarisingHandler
qualname=mypackage

[handler_consoleHandler]
class=StreamHandler
#level=INFO
formatter=consoleFormatter
args=(sys.stdout,)

[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=mypackageFormatter
args=('mypackage.log', 'M', 1, 5)

[handler_summarisingHandler]
class=mailinglogger.SummarisingLogger
level=INFO
formatter=mypackageFormatter
args=('[email protected]', ('[email protected]',), 'relay.somewhere.com')

#mypackage/scripts/main.py:
import logging
import logging.config
import os
import sys

import mypackage.module1
import mypackage.module2

logging.config.fileConfig('data/mypackage.logging.conf')
log = logging.getLogger(__name__)

if __== '__main__':
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    logging.getLogger('').setLevel(getattr(logging, loglevel))
    # or logging.getLogger('mypackage').setLevel(getattr(logging, loglevel)) ?

    mypackage.module1.do_something()
    mypackage.module2.do_something_else()

#mypackage/module1.py:
import logging

log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def do_something():
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

#mypackage/module2.py:
import logging

log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def do_something_else():
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

UPDATE 1

En attendant, j'ai découvert cette réponse et j'ai modifié mon code avec succès de cette façon:

#mypackage/scripts/main.py:
import logging
import logging.config
import os
import sys
import mailinglogger

import mypackage.module1
import mypackage.module2

def main():
    # get the console log level from the command-line
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    # create formatters
    simple_formatter = logging.Formatter("%(name)s:%(levelname)s: %(message)s")
    detailed_formatter = logging.Formatter("%(asctime)s %(name)s[%(process)d]: %(levelname)s - %(message)s")

    # get a top-level "mypackage" logger,
    # set its log level to DEBUG,
    # BUT PREVENT IT from propagating messages to the root logger
    #
    log = logging.getLogger('mypackage')
    log.setLevel(logging.DEBUG)
    log.propagate = 0

    # create a console handler
    # and set its log level to the command-line option 
    # 
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(getattr(logging, loglevel))
    console_handler.setFormatter(simple_formatter)

    # create a file handler
    # and set its log level to DEBUG
    #
    file_handler = logging.handlers.TimedRotatingFileHandler('mypackage.log', 'M', 1, 5)
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(detailed_formatter)

    # create a mail handler
    # and set its log level to INFO
    #
    mail_handler = mailinglogger.SummarisingLogger(
        '[email protected]', ('[email protected]',), 'relay.somewhere.com')
    mail_handler.setLevel(logging.INFO)
    mail_handler.setFormatter(detailed_formatter)

    # add handlers to the "mypackage" logger
    #
    log.addHandler(console_handler)
    log.addHandler(file_handler)
    log.addHandler(mail_handler)

    # let the modules do their stuff 
    # and log to the "mypackage.module1" and "mypackage.module2" loggers
    #
    mypackage.module1.do_something()
    mypackage.module2.do_something_else()


if __== '__main__':
    main()

Maintenant, je vais essayer de traduire cela dans un fichier logging.config ...


UPDATE 2

Voici la meilleure combinaison de configuration et de code de journalisation que j'ai trouvée.

Dans le fichier mypackage.logging.conf, le consignateur "mypackage" est:

  • mis en place uniquement avec les gestionnaires de fichiers et de courrier électronique;
  • sa propagation est définie sur false;
  • son niveau est défini sur DEBUG;
  • tandis que les gestionnaires de fichiers et de messagerie sont respectivement définis sur INFO et DEBUG.

#mypackage/data/mypackage.logging.conf
[loggers]
keys=root,mypackage

[handlers]
keys=consoleHandler,timedRotatingFileHandler,summarisingHandler

[formatters]
keys=simpleFormatter,consoleFormatter,mypackageFormatter

[logger_root]
#level=INFO
handlers=consoleHandler

[logger_mypackage]
level=DEBUG
handlers=timedRotatingFileHandler,summarisingHandler
qualname=mypackage
propagate=0

[handler_consoleHandler]
class=StreamHandler
#level=INFO
formatter=consoleFormatter
args=(sys.stdout,)

[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=mypackageFormatter
args=('mypackage.log', 'M', 1, 5)

[handler_summarisingHandler]
class=mailinglogger.SummarisingLogger
level=INFO
formatter=mypackageFormatter
args=('[email protected]', ('[email protected]',), 'relay.somewhere.com')

[formatter_consoleFormatter]
format=%(levelname)s: %(message)s
datefmt=

[formatter_mypackageFormatter]
format=%(asctime)s %(name)s[%(process)d]: %(levelname)s - %(message)s
datefmt=

Dans le script:

  1. la configuration de la journalisation est lue;

  2. un format de console est (re) créé;

  3. un gestionnaire de console est créé avec le niveau de journalisation à partir de l'option de ligne de commande, puis ajouté au consignateur "mypackage".


import logging
import logging.config
import os
import sys

import mypackage.module1
import mypackage.module2

def setup_logging(loglevel):
    #
    # load logging config from file
    #
    logging.config.fileConfig('data/mypackage.logging.conf')

    # (re-)create formatter
    console_formatter = logging.Formatter("%(name)s:%(levelname)s: %(message)s")

    # create a console handler
    # and set its log level to the command-line option 
    # 
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(console_formatter)
    console_handler.setLevel(getattr(logging, loglevel))

    # add console handler to the pre-configured "mypackage" logger
    #
    logger = logging.getLogger('mypackage')
    logger.addHandler(console_handler)


def main():
    # get the console log level from the command-line
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    # load logging config and setup console handler
    #
    setup_logging(loglevel)

    # log from the script to the "mypackage.scripts.main" logger
    #
    log = logging.getLogger(__name__)
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

    # let the modules do their stuff 
    # and log to the "mypackage.module1" and "mypackage.module2" loggers
    #
    mypackage.module1.do_something()
    mypackage.module2.do_something_else()

if __name__== '__main__':
    main()

Les choses seraient plus simples si les gestionnaires étaient "adressables" par leur nom lorsqu'ils étaient chargés depuis un fichier de configuration.

Ensuite, nous pourrions avoir le gestionnaire de console mypackage configuré dans le fichier de configuration et son niveau de journalisation modifié dans le code, comme suit:

def setup_logging(loglevel):
    logging.config.fileConfig('data/mypackage.logging.conf')

    logger = logging.getLogger('mypackage')
    console_handler = logger.getHandler('consoleHandler')
    console_handler.setLevel(getattr(logging, loglevel))

Il n'y aurait pas besoin de recréer un formateur non plus ...

(dernière mise à jour: oui, je connais https://docs.python.org/3/library/logging.config.html#incremental-configuration , mais dans ce cas, je suis coincé avec Python 2.6 ... :-)

14
Georges Martin

Une approche pour mettre à jour votre gestionnaire:

import logging

from rootmodule.mymodule import mylogger

def update_handler_level(logger,  handler_type, level="INFO"):
    # if not root logger user logger.parent
    for handler in logger.handlers or logger.parent.handlers:
        if isinstance(handler, handler_type):
            print(handler.level)
            handler.setLevel(getattr(logging, level, "INFO"))
            print(handler.level)

mylogger.debug('test')
update_handler_level(mylogger, logging.StreamHandler)
mylogger.debug('test')

Mon logging.cfg est assez similaire au votre sauf que le nom du logger est défini dans un module constant (un module peut-être renommé sans casser la configuration de la journalisation)

Pour mettre à jour à partir de la ligne de commande, vous devez avoir un mappage entre votre valeur opts et le nom de la sous-classe logging.Handler. 

1
Ali SAID OMAR