Question

Je voudrais avoir loglevel TRACE (5) pour mon application, je ne pense pas que debug() est suffisante. De plus log(5, msg) n'est pas ce que je veux. Comment puis-je ajouter un loglevel personnalisé à un enregistreur 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")

Maintenant, je voudrais 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 qui est , à mon humble avis, une excellente solution basée sur la très bonne proposition de Eric S.

Était-ce utile?

La solution

@Eric S.

La 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 niveau de journalisation est réglé sur. Donc, si vous faites un nouveau numéro de niveau de 9, si vous appelez setLevel(50), niveau inférieur messages seront imprimés par erreur.

  

Pour éviter que cela se produise, vous avez besoin une autre ligne dans la fonction « debugv » pour vérifier si le niveau d'enregistrement en question est effectivement activée.

exemple fixe qui vérifie si le niveau d'enregistrement est activé:

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    if self.isEnabledFor(DEBUG_LEVELV_NUM):
        # Yes, logger takes its '*args' as 'args'.
        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, .debug, etc.).

Je ne 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. =)

Autres conseils

Je pris la « éviter de voir lambda » réponse et a dû modifier où le log_at_my_log_level a été ajouté. Moi aussi vu le problème que Paul ne « Je ne pense pas que cela fonctionne. Ne pas vous avez besoin enregistreur comme le premier arg 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

La combinaison de toutes les réponses existantes avec un tas d'expérience d'utilisation, je pense que je suis venu avec une liste de toutes les choses qui doivent être fait pour garantir 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é comme attribut à lui-même logging 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é à la classe de l'enregistreur actuellement configuré. Étant donné que ce n'est pas 100% garanti à logging.Logger, utilisez logging.getLoggerClass() à la place.

Toutes les étapes sont illustrées dans le procédé 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)

Cette question est assez vieux, mais je ne me traite le même sujet et a 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)

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')

Je trouve plus facile de créer un nouvel attribut de l'objet enregistreur qui passe la fonction log (). Je pense que le module enregistreur fournit la addLevelName () et le journal () pour cette raison. Par conséquent, aucune sous-classes ou nouvelle méthode 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

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

devrait fonctionner comme prévu.

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

  1. Ne pas utiliser _log, utilisez log (vous ne devez pas vérifier isEnabledFor)
  2. le module d'enregistrement doit être une instance de créer 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 tout stocker
# 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 devrez setFormatter, setHandler et setLevel(TRACE) sur le handler et sur le log lui-même Sé fait cette trace de bas niveau

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'aide lambda ressemble un peu plus propre, mais l'inconvénient est qu'il ne peut pas prendre des arguments clés. Je ne l'ai jamais utilisé moi-même, donc pas trop grave.

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

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 ne l'ai jamais essayé de travailler avec une classe d'enregistreur autonome, mais je pense que l'idée de base est la même (utilisation _log).

Ajout d'exemple pour obtenir Mad Physiciens nom du fichier et le numéro de ligne correct:

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

Alors que nous avons déjà beaucoup de réponses, ce qui suit est à mon avis plus pythonique:

import logging

from functools import partial, partialmethod

logging.TRACE = 5
logging.addLevelName(logging.TRACE, 'TRACE')
logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE)
logging.trace = partial(logging.log, logging.TRACE)

Si vous voulez utiliser mypy sur votre code, il est recommandé d'ajouter # type: ignore pour supprimer les avertissements de l'ajout d'attribut.

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')

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: racine: y1

     

TRACE: racine: y2

fonction de la réponse épinglé, je l'ai écrit une petite méthode qui crée de nouveaux niveaux de automaticaly exploitation forestière

def set_custom_logging_levels(config={}):
    """
        Assign custom levels for logging
            config: is a dict, like
            {
                'EVENT_NAME': EVENT_LEVEL_NUM,
            }
        EVENT_LEVEL_NUM can't be like already has logging module
        logging.DEBUG       = 10
        logging.INFO        = 20
        logging.WARNING     = 30
        logging.ERROR       = 40
        logging.CRITICAL    = 50
    """
    assert isinstance(config, dict), "Configuration must be a dict"

    def get_level_func(level_name, level_num):
        def _blank(self, message, *args, **kws):
            if self.isEnabledFor(level_num):
                # Yes, logger takes its '*args' as 'args'.
                self._log(level_num, message, args, **kws) 
        _blank.__name__ = level_name.lower()
        return _blank

    for level_name, level_num in config.items():
        logging.addLevelName(level_num, level_name.upper())
        setattr(logging.Logger, level_name.lower(), get_level_func(level_name, level_num))

config peut SMTH comme ça:

new_log_levels = {
    # level_num is in logging.INFO section, that's why it 21, 22, etc..
    "FOO":      21,
    "BAR":      22,
}

Dans le cas où 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()

    setattr(custom_log_module.Logger, log_call, cust_log)
    return custom_log_module
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top