Mis a jour le 2025-04-14, 12:10

Logging

On peut utiliser le module standard logging : import logging
Différents niveaux, le niveau par défaut étant WARNING et tout appel d'un niveau supérieur à celui défini produira un log :
  • DEBUG : logging.debug('message')
  • INFO : logging.info('message')
  • WARNING : logging.warning('message')
  • ERROR : logging.error('message')
  • CRITICAL : logging.critical('message')
Pour régler le niveau de debug (et aussi le fichier d'output) :
  • logging.basicConfig(filename = 'myFile.log', level = logging.DEBUG)
  • les logs sont toujours rajoutés au fichier.
  • si on veut effacer le fichier de log à chaque run, faire : logging.basicConfig(filename = 'myFile.log', filemode = 'w')
  • attention, seulement un appel, les autres seront ignorés !
  • attention, même si le niveau n'est pas suffisant pour générer le message, ce qui se trouve dans l'appel au logging est quand même évalué ! Et si ca finit en erreur, l'exception est levée.
On peut formatter les messages : logging.basicConfig(level = logging.WARNING, format = '%(levelname)s:%(asctime)s:%(message)s', datefmt = '%Y-%m-%d %H:%M:%S')
  • donne ici quelque chose comme : ERROR:2023-11-18 09:24:41:my message
  • le format de date est le même que celui de time.strftime().
En général, on souhaite indiquer la provenance du logging :
  • on crée un logger en lui donnant un nom : logger = logging.getLogger('myName')
  • en général, on utilise : logger = logging.getLogger(__name__), mais on peut faire ce que l'on veut.
  • ce nom apparaît par défaut dans le log. Si on donne un format, utiliser par exemple : logging.basicConfig(format = '%(levelname)s:%(name)s:%(message)s')
  • on peut fixer le niveau d'un logger : logger = logging.getLogger('toto'); logger.setLevel(logging.INFO)
  • logger2.log(logging.WARNING, 'message') : permet la fixation dynamique du niveau de debug.
  • les loggers sont organisés en hierarchie avec des noms séparés par des points :
    logging.basicConfig()
    logger1 = logging.getLogger('toto')
    logger2 = logging.getLogger('toto.titi')
    logger1.setLevel(logging.INFO)
    logger1.info('from logger1')
    logger2.info('from logger2')
          
    les 2 messages sont affichés. Attention, il faut appeler basicConfig avant dans une configuration simple sans handlers !
Handlers : on définit un handler qui va s'occuper des messages, et on ajoute à logger un ou plusieurs handlers : le logger va passer les messages au handler qui va les traiter.
  • StreamHandler : typiquement pour afficher sur la console, par défaut stderr : handler = logging.StreamHandler()
  • FileHandler : handler = logging.FileHandler('mylog', mode = 'a') (mode = 'a' est le défaut, sinon, on peut utiliser 'w').
  • handler = logging.NullHandler() : ne loggue pas du tout, utile dans une librairie pour laisser le choix à l'utilisateur de logguer ou non.
  • il y a beaucoup d'autres handlers, par exemple RotatingFileHandler et on peut implémenter ses propres handler.
  • logger.addHandler(myHandler) : rajoute un handler au logger. Les loggers fils en héritent aussi
  • on peut fixer un level au handler, qui vient se rajouter au level du logger : myHandler.setLevel(logger.WARNING)
  • exemple un peu complexe :
    handler1 = logging.StreamHandler()
    handler2 = logging.FileHandler('mylog', mode = 'a')
    handler1.setFormatter(logging.Formatter(fmt = '%(name)s - %(levelname)s - %(message)s'))
    handler2.setFormatter(logging.Formatter(fmt = '%(name)s - %(message)s'))
    handler1.setLevel(logging.WARNING)
    logger1 = logging.getLogger('toto')
    logger1.addHandler(handler1)
    logger2 = logging.getLogger('toto.titi')
    logger2.addHandler(handler2)
    logger1.setLevel(logging.INFO)
    logger1.info('from logger1')
    logger2.info('from logger2 (info)')
    logger2.warning('from logger2 (warning)')
        
Si on configure un FileHandler, on a quand même aussi du log sur stderr car par défaut, le root logger a un StreamHandler défini (cf valeur de logging.getLogger().handlers). Pour éviter le logging sur la console :
  • soit enlever ce handler : logging.getLogger().removeHandler(logging.getLogger().handlers[0])
  • ou alors éviter la propagation : logger.propagate = False
Valeurs utilisables dans l'argument fmt des Formatters :
  • %(message)s : le message.
  • %(name)s : le nom du logger.
  • %(asctime)s : date + heure.
  • %(filename)s : le nom du fichier qui a généré le message.
  • %(funcName)s : la fonction qui a généré le message.
  • %(lineno)d : le numéro de ligne qui a généré le message.
  • %(process)d : le process id.
  • %(thread)d : le thread id.
Si l'action du logging est un peu couteuse, on peut tester si elle doit être faite :
if logger.isEnabledFor(logging.DEBUG):
  logger.debug('Message with %s', myLongFunction())
  

Copyright python-simple.com
programmer en python, tutoriel python, graphes en python, Aymeric Duclert