2010-02-02 4 views
70

Je voudrais avoir loglevel TRACE (5) pour mon application, car je ne pense pas que debug() est suffisant. De plus log(5, msg) n'est pas ce que je veux. Comment puis-je ajouter un loglevel personnalisé à un logger Python?Comment ajouter un niveau de log personnalisé à la fonction de journalisation de Python

J'ai un mylogger.py avec le contenu suivant:

import logging 

@property 
def log(obj): 
    myLogger = logging.getLogger(obj.__class__.__name__) 
    return myLogger 

Dans mon code, je l'utiliser de la manière suivante:

class ExampleClass(object): 
    from mylogger import log 

    def __init__(self): 
     '''The constructor with the logger''' 
     self.log.debug("Init runs") 

J'aimerais maintenant appeler self.log.trace("foo bar")

Merci d'avance pour votre aide.

Modifier (8 décembre 2016): J'ai changé la réponse acceptée à pfa's qui est, à mon humble avis, une excellente solution basée sur la très bonne proposition de Eric S.

Répondre

112

@Eric S.

réponse de

Eric S. est excellent, mais je l'ai appris par l'expérience que cela provoquera toujours des messages enregistrés au niveau de débogage à imprimer - quel que soit le le niveau de journalisation est défini sur.Donc, si vous faites un nouveau numéro de niveau 9, si vous appelez setLevel (50), les messages de niveau inférieur seront imprimés par erreur. Pour éviter cela, vous avez besoin d'une autre ligne dans la fonction "debugv" pour vérifier si le niveau de journalisation en question est réellement activé.

exemple fixe qui vérifie si le niveau de journalisation est activée:

import logging 
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV") 
def debugv(self, message, *args, **kws): 
    # Yes, logger takes its '*args' as 'args'. 
    if self.isEnabledFor(DEBUG_LEVELV_NUM): 
     self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv 

Si vous regardez le code pour class Logger en logging.__init__.py pour Python 2.7, c'est ce que toutes les fonctions du journal standard ne (.critical,. débogage, etc.).

Je peux apparemment pas envoyer des réponses aux réponses des autres par manque de réputation ... espérons-Eric mettra à jour son poste s'il voit. =)

+7

C'est la meilleure réponse car elle vérifie correctement le niveau de journalisation. –

+3

Cela devrait être la réponse acceptée ... –

+2

Certainement beaucoup plus informatif que la réponse actuelle. –

8

Je pense que vous aurez à sous-classe la classe Logger et ajouter une méthode appelée trace qui appelle essentiellement Logger.log avec un niveau inférieur à DEBUG. Je n'ai pas essayé ceci mais c'est ce que le docs indicate.

+3

Et vous aurez probablement envie de remplacer 'logging.getLogger' pour retourner votre sous-classe au lieu de la classe intégrée . –

+0

ok, avez-vous d'autres bonnes pratiques pour 'logging'? – tuergeist

+4

@ S.Lott - En fait (au moins avec la version actuelle de Python, peut-être que ce n'était pas le cas en 2010), vous devez utiliser ['setLoggerClass (MyClass)'] (https://docs.python.org/ 3/library/logging.html? Highlight = logging # logging.setLoggerClass) puis appelez 'getLogger()' comme d'habitude ... – mac

9

Je trouve plus facile de créer un nouvel attribut pour l'objet de consignateur qui transmet la fonction log(). Je pense que le module logger fournit le addLevelName() et le log() pour cette raison. Ainsi, aucune sous-classe ou nouvelle méthode n'est nécessaire.

import logging 

@property 
def log(obj): 
    logging.addLevelName(5, 'TRACE') 
    myLogger = logging.getLogger(obj.__class__.__name__) 
    setattr(myLogger, 'trace', lambda *args: myLogger.log(5, *args)) 
    return myLogger 

maintenant

mylogger.trace('This is a trace message') 

devrait fonctionner comme prévu.

+0

Cela n'aurait-il pas un petit impact sur la performance par rapport au sous-classement? Avec cette approche, chaque fois que certains demandent un enregistreur, ils devront faire l'appel de setattr. Vous les intégrez probablement dans une classe personnalisée, mais néanmoins, setattr doit être appelé sur chaque enregistreur créé, n'est-ce pas? –

+0

@ Zbigniew ci-dessous a indiqué que cela n'a pas fonctionné, ce que je pense est parce que votre enregistreur doit faire son appel à '_log', pas' log'. – marqueed

2

Dans mon expérience, c'est la solution complète du problème de l'op ... pour éviter de voir « lambda » comme la fonction dans laquelle le message est émis, aller plus loin:

MY_LEVEL_NUM = 25 
logging.addLevelName(MY_LEVEL_NUM, "MY_LEVEL_NAME") 
def log_at_my_log_level(self, message, *args, **kws): 
    # Yes, logger takes its '*args' as 'args'. 
    self._log(MY_LEVEL_NUM, message, args, **kws) 
logger.log_at_my_log_level = log_at_my_log_level 

Je n'ai jamais J'ai essayé de travailler avec une classe logger autonome, mais je pense que l'idée de base est la même (utiliser _log).

+0

Je ne pense pas que cela fonctionne. Vous n'avez pas besoin de 'logger' comme premier argument dans' log_at_my_log_level'? – Paul

+0

Oui, je pense que vous le feriez probablement.Cette réponse a été adaptée à partir du code qui résout un problème légèrement différent. – marqueed

53

J'ai pris la réponse "avoid seeing lambda" et j'ai dû modifier l'endroit où log_at_my_log_level était ajouté. Moi aussi, j'ai vu le problème que Paul a fait "Je ne pense pas que cela fonctionne. N'avez-vous pas besoin de logger comme premier argument dans log_at_my_log_level?" Cela a fonctionné pour moi

import logging 
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV") 
def debugv(self, message, *args, **kws): 
    # Yes, logger takes its '*args' as 'args'. 
    self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv 
+4

+1. C'est la bonne réponse. – Macke

+7

+1 aussi. Une approche élégante, et cela a parfaitement fonctionné. Une note importante: *** Vous n'avez besoin de le faire qu'une fois, dans un seul module, et cela fonctionnera pour tous les modules ***. Vous n'avez même pas besoin d'importer le module "setup". Alors jetez ceci dans le paquet __init __. Py d'un paquet et soyez heureux: D – MestreLion

+4

@Eric S. Vous devriez jeter un oeil à cette réponse: http://stackoverflow.com/a/13638084/600110 –

2

Cela a fonctionné pour moi:

import logging 
logging.basicConfig(
    format=' %(levelname)-8.8s %(funcName)s: %(message)s', 
) 
logging.NOTE = 32 # positive yet important 
logging.addLevelName(logging.NOTE, 'NOTE')  # new level 
logging.addLevelName(logging.CRITICAL, 'FATAL') # rename existing 

log = logging.getLogger(__name__) 
log.note = lambda msg, *args: log._log(logging.NOTE, msg, args) 
log.note('school\'s out for summer! %s', 'dude') 
log.fatal('file not found.') 

La question lambda/funcName est fixé par logger._log comme @marqueed souligné. Je pense que l'utilisation de lambda a l'air un peu plus propre, mais l'inconvénient est qu'il ne peut pas prendre des arguments de mots-clés. Je n'ai jamais utilisé ça moi-même, donc pas de biggie.

 
    NOTE  setup: school's out for summer! dude 
    FATAL setup: file not found. 
16

qui a commencé la mauvaise pratique d'utiliser des méthodes internes (self._log) et pourquoi chaque réponse basée sur cette ?! La solution pythonique serait d'utiliser self.log à la place que vous ne devez pas salir avec une substance interne:

import logging 

SUBDEBUG = 5 
logging.addLevelName(SUBDEBUG, 'SUBDEBUG') 

def subdebug(self, message, *args, **kws): 
    self.log(SUBDEBUG, message, *args, **kws) 
logging.Logger.subdebug = subdebug 

logging.basicConfig() 
l = logging.getLogger() 
l.setLevel(SUBDEBUG) 
l.subdebug('test') 
l.setLevel(logging.DEBUG) 
l.subdebug('test') 
+16

Utiliser _log() au lieu de log() est nécessaire pour évitez d'introduire un niveau supplémentaire dans la pile d'appels. Si log() est utilisé, l'introduction du cadre de pile supplémentaire entraîne plusieurs attributs de LogRecord (funcName, lineno, filename, pathname, ...) pour pointer vers la fonction de débogage au lieu de l'appelant réel. Ce n'est probablement pas le résultat souhaité. – rivy

+4

Depuis quand l'appel des méthodes internes d'une classe n'est-il pas autorisé? Ce n'est pas parce que la fonction est définie en dehors de la classe que c'est une méthode externe. – OozeMeister

+3

Cette méthode modifie non seulement la trace de la pile inutilement, mais ne vérifie pas non plus que le niveau correct est en cours de journalisation. –

0

Comme alternative à l'ajout d'une méthode supplémentaire pour la classe Logger Je recommande d'utiliser la méthode Logger.log(level, msg).

import logging 

TRACE = 5 
logging.addLevelName(TRACE, 'TRACE') 
FORMAT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s' 


logging.basicConfig(format=FORMAT) 
l = logging.getLogger() 
l.setLevel(TRACE) 
l.log(TRACE, 'trace message') 
l.setLevel(logging.DEBUG) 
l.log(TRACE, 'disabled trace message') 
25

Cette question est un peu vieux, mais je ne me portait sur le même sujet et trouvé une façon similaire à ceux déjà mentionnés qui semble un peu plus propre. Cela a été testé sur 3.4, donc je ne suis pas sûr que les méthodes utilisées existent dans les anciennes versions:

from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET 

VERBOSE = 5 

class MyLogger(getLoggerClass()): 
    def __init__(self, name, level=NOTSET): 
     super().__init__(name, level) 

     addLevelName(VERBOSE, "VERBOSE") 

    def verbose(self, msg, *args, **kwargs): 
     if self.isEnabledFor(VERBOSE): 
      self._log(VERBOSE, msg, args, **kwargs) 

setLoggerClass(MyLogger) 
+0

Ceci est à mon humble avis la meilleure réponse, car elle évite patch de singe. Que font exactement 'get' et' setLoggerClass' et pourquoi sont-ils nécessaires? –

+1

@MarcoSulla Elles sont documentées dans le cadre du module de journalisation de Python. Le sous-classement dynamique, je suppose, est utilisé au cas où quelqu'un voudrait son propre llogger en utilisant cette bibliothèque. Ce MyLogger deviendrait alors une sous-classe de ma classe, combinant les deux. – CrackerJack9

13

Combinant toutes les réponses existantes avec un tas d'expérience d'utilisation, je pense que je suis venu avec un liste de toutes les choses qui doivent être faites pour assurer une utilisation complètement transparente du nouveau niveau. Les étapes ci-dessous supposent que vous ajoutez un nouveau niveau TRACE avec la valeur logging.DEBUG - 5 == 5:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') doit être invoqué pour obtenir le nouveau niveau enregistré en interne afin qu'il puisse être référencé par son nom.
  2. Le nouveau niveau doit être ajouté en tant qu'attribut à logging lui-même pour la cohérence: logging.TRACE = logging.DEBUG - 5.
  3. une méthode appelée trace doit être ajouté au module logging. Il doit se comporter comme debug, info, etc.
  4. Une méthode appelée trace doit être ajoutée à la classe de consignateur actuellement configurée. Étant donné que ce n'est pas garantie à 100% être logging.Logger, utilisez plutôt logging.getLoggerClass().

Toutes les étapes sont illustrées dans la méthode ci-dessous:

def addLoggingLevel(levelName, levelNum, methodName=None): 
    """ 
    Comprehensively adds a new logging level to the `logging` module and the 
    currently configured logging class. 

    `levelName` becomes an attribute of the `logging` module with the value 
    `levelNum`. `methodName` becomes a convenience method for both `logging` 
    itself and the class returned by `logging.getLoggerClass()` (usually just 
    `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is 
    used. 

    To avoid accidental clobberings of existing attributes, this method will 
    raise an `AttributeError` if the level name is already an attribute of the 
    `logging` module or if the method name is already present 

    Example 
    ------- 
    >>> addLoggingLevel('TRACE', logging.DEBUG - 5) 
    >>> logging.getLogger(__name__).setLevel("TRACE") 
    >>> logging.getLogger(__name__).trace('that worked') 
    >>> logging.trace('so did this') 
    >>> logging.TRACE 
    5 

    """ 
    if not methodName: 
     methodName = levelName.lower() 

    if hasattr(logging, levelName): 
     raise AttributeError('{} already defined in logging module'.format(levelName)) 
    if hasattr(logging, methodName): 
     raise AttributeError('{} already defined in logging module'.format(methodName)) 
    if hasattr(logging.getLoggerClass(), methodName): 
     raise AttributeError('{} already defined in logger class'.format(methodName)) 

    # This method was inspired by the answers to Stack Overflow post 
    # http://stackoverflow.com/q/2183233/2988730, especially 
    # http://stackoverflow.com/a/13638084/2988730 
    def logForLevel(self, message, *args, **kwargs): 
     if self.isEnabledFor(levelNum): 
      self._log(levelNum, message, args, **kwargs) 
    def logToRoot(message, *args, **kwargs): 
     logging.log(levelNum, message, *args, **kwargs) 

    logging.addLevelName(levelNum, levelName) 
    setattr(logging, levelName, levelNum) 
    setattr(logging.getLoggerClass(), methodName, logForLevel) 
    setattr(logging, methodName, logToRoot) 
+0

Trier les réponses par 'Oldest', et vous apprécierez que c'est la meilleure réponse de tous! –

+0

Merci. J'ai fait pas mal de travail en bricolant quelque chose comme ça et cet AQ m'a beaucoup aidé, alors j'ai essayé d'ajouter quelque chose. –

1

Conseils pour la création d'un enregistreur personnalisé:

  1. Ne pas utiliser _log, utilisez log (vous n'avez pas check isEnabledFor)
  2. Le module de journalisation doit être l'instance de création de l'enregistreur personnalisé car il fait un peu de magie dans getLogger, vous aurez donc besoin de définir la classe via setLoggerClass
  3. Vous n'avez pas besoin de définir __init__ pour l'enregistreur, classe si vous n'êtes pas stocker quoi que ce soit
# Lower than debug which is 10 
TRACE = 5 
class MyLogger(logging.Logger): 
    def trace(self, msg, *args, **kwargs): 
     self.log(TRACE, msg, *args, **kwargs) 

Lorsque vous appelez cette utilisation de l'enregistreur setLoggerClass(MyLogger) pour faire de ce l'enregistreur par défaut de getLogger

logging.setLoggerClass(MyLogger) 
log = logging.getLogger(__name__) 
# ... 
log.trace("something specific") 

Vous aurez besoin de setFormatter, setHandler et setLevel(TRACE) sur le handler et sur le log lui-même Sé fait cette trace de bas niveau

1

Addition à Mad Physiciens exemple pour obtenir le nom du fichier et le numéro de ligne correcte:

def logToRoot(message, *args, **kwargs): 
    if logging.root.isEnabledFor(levelNum): 
     logging.root._log(levelNum, message, args, **kwargs) 
-1

Si quelqu'un veut un moyen automatisé d'ajouter un nouveau niveau de journalisation au module d'enregistrement (ou une copie de celui-ci) de manière dynamique, j'ai créé cette fonction, en expansion @ réponse de pfa:

def add_level(log_name,custom_log_module=None,log_num=None, 
       log_call=None, 
        lower_than=None, higher_than=None, same_as=None, 
       verbose=True): 
    ''' 
    Function to dynamically add a new log level to a given custom logging module. 
    <custom_log_module>: the logging module. If not provided, then a copy of 
     <logging> module is used 
    <log_name>: the logging level name 
    <log_num>: the logging level num. If not provided, then function checks 
     <lower_than>,<higher_than> and <same_as>, at the order mentioned. 
     One of those three parameters must hold a string of an already existent 
     logging level name. 
    In case a level is overwritten and <verbose> is True, then a message in WARNING 
     level of the custom logging module is established. 
    ''' 
    if custom_log_module is None: 
     import imp 
     custom_log_module = imp.load_module('custom_log_module', 
              *imp.find_module('logging')) 
    log_name = log_name.upper() 
    def cust_log(par, message, *args, **kws): 
     # Yes, logger takes its '*args' as 'args'. 
     if par.isEnabledFor(log_num): 
      par._log(log_num, message, args, **kws) 
    available_level_nums = [key for key in custom_log_module._levelNames 
          if isinstance(key,int)] 

    available_levels = {key:custom_log_module._levelNames[key] 
          for key in custom_log_module._levelNames 
          if isinstance(key,str)} 
    if log_num is None: 
     try: 
      if lower_than is not None: 
       log_num = available_levels[lower_than]-1 
      elif higher_than is not None: 
       log_num = available_levels[higher_than]+1 
      elif same_as is not None: 
       log_num = available_levels[higher_than] 
      else: 
       raise Exception('Infomation about the '+ 
           'log_num should be provided') 
     except KeyError: 
      raise Exception('Non existent logging level name') 
    if log_num in available_level_nums and verbose: 
     custom_log_module.warn('Changing ' + 
            custom_log_module._levelNames[log_num] + 
            ' to '+log_name) 
    custom_log_module.addLevelName(log_num, log_name) 

    if log_call is None: 
     log_call = log_name.lower() 
    exec('custom_log_module.Logger.'+eval('log_call')+' = cust_log', None, locals()) 
    return custom_log_module 
+1

Eval inside exec. Sensationnel. –

+1

..... Je ne sais pas ce qui m'a fait faire cela .... après tant de mois, je serais heureux d'échanger cette déclaration avec un 'setattr' à la place ... –

0

Je suis confus; avec Python 3.5, au moins, il fonctionne:

import logging 


TRACE = 5 
"""more detail than debug""" 

logging.basicConfig() 
logging.addLevelName(TRACE,"TRACE") 
logger = logging.getLogger('') 
logger.debug("n") 
logger.setLevel(logging.DEBUG) 
logger.debug("y1") 
logger.log(TRACE,"n") 
logger.setLevel(TRACE) 
logger.log(TRACE,"y2") 

sortie:

DEBUG:root:y1

TRACE:root:y2

Questions connexes