J'aimerais utiliser loglevel TRACE (5) pour mon application, car je ne pense pas que debug()
soit suffisant. De plus, log(5, msg)
n'est pas ce que je veux. Comment puis-je ajouter un niveau de journalisation personnalisé à un enregistreur 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'utilise 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.
Edit (8 décembre 2016): J'ai changé la réponse acceptée en pfa's qui est, à mon humble avis, une excellente solution basée sur la très bonne proposition de Eric S.
@ Eric S.
La réponse d'Eric S. est excellente, mais j'ai appris expérimentalement que les messages enregistrés au nouveau niveau de débogage seront toujours imprimés, quel que soit le niveau défini pour le niveau de consignation. Ainsi, si vous créez un nouveau numéro de niveau de 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 à l'intérieur de 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é:
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 examinez le code pour class Logger
dans logging.__init__.py
pour Python 2.7, toutes les fonctions de journalisation standard (.critical, .debug, etc.) remplissent cette fonction.
Je ne peux apparemment pas répondre aux réponses des autres par manque de réputation ... J'espère que Eric mettra à jour son message s'il le voit. =)
J'ai pris la réponse "éviter de voir lambda" et j'ai dû modifier l'emplacement où le log_at_my_log_level était ajouté. J'ai moi aussi vu le problème rencontré par Paul "Je ne pense pas que cela fonctionne. N'avez-vous pas besoin de logger en tant que premier argument de 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
Cette question est assez ancienne, mais je viens de traiter du même sujet et de trouver un moyen similaire à ceux déjà mentionnés qui me parait un peu plus propre. Ceci a été testé sur 3.4, donc je ne suis pas sûr que les méthodes utilisées existent dans les versions précédentes:
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)
En combinant toutes les réponses existantes avec de nombreuses expériences d'utilisation, je pense avoir dressé une liste de toutes les tâches à effectuer pour garantir une utilisation totalement transparente du nouveau niveau. Les étapes ci-dessous supposent que vous ajoutez un nouveau niveau TRACE
avec la valeur logging.DEBUG - 5 == 5
:
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
doit être appelé pour obtenir le nouveau niveau enregistré en interne afin qu'il puisse être référencé par nom.logging
lui-même pour assurer la cohérence: logging.TRACE = logging.DEBUG - 5
.trace
doit être ajoutée au module logging
. Il devrait se comporter exactement comme debug
, info
, etc.trace
doit être ajoutée à la classe de journalisation actuellement configurée. Puisque ce n'est pas garanti à 100% d'ê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)
Qui a commencé la mauvaise pratique d'utiliser des méthodes internes (self._log
) et pourquoi chaque réponse est-elle basée sur ça?! La solution Pythonic consisterait à utiliser self.log
afin de ne pas avoir à vous soucier de choses internes:
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 pour l'objet logger qui transmet la fonction log (). Je pense que le module de journalisation fournit le addLevelName () et le log () pour cette raison même. 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
à présent
mylogger.trace('This is a trace message')
devrait fonctionner comme prévu.
Je pense que vous devrez sous-classer la classe Logger
et ajouter une méthode appelée trace
qui appelle fondamentalement Logger.log
avec un niveau inférieur à DEBUG
. Je n’ai pas essayé cela, mais c’est ce que les docs indiquent .
Conseils pour créer un enregistreur personnalisé:
_log
, utilisez log
(vous n'avez pas à vérifier isEnabledFor
)getLogger
; vous devrez donc définir la classe via setLoggerClass
__init__
pour le consignateur, classe si vous ne stockez rien# 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 cet enregistreur, utilisez setLoggerClass(MyLogger)
pour en faire l’enregistreur par défaut à partir de getLogger
.
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
Vous aurez besoin de setFormatter
, setHandler
et setLevel(TRACE)
sur handler
et sur log
pour que cette trace de bas niveau apparaisse réellement.
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.')
Le problème de lambda/funcName est résolu avec logger._log comme l'a souligné @marqueed. Je pense que l'utilisation de lambda semble un peu plus propre, mais l'inconvénient est qu'elle ne peut pas prendre d'argument de mot clé. Je n'ai jamais utilisé ça moi-même, donc pas grave.
NOTE setup: l'école est finie pour l'été! mec Configuration FATAL: fichier non trouvé .
D'après mon expérience, c'est la solution complète au problème de l'op ... Pour éviter de voir "lambda" comme fonction dans laquelle le message est émis, allez 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 essayé de travailler avec une classe de journalisation autonome, mais je pense que l'idée de base est la même (utilisez _log).
Ajout à l’exemple de Mad Physicists pour obtenir le nom de fichier et le numéro de ligne corrects:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
Bien que nous ayons déjà plein de réponses correctes, ce qui suit est à mon avis un peu 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 souhaitez utiliser mypy
dans votre code, il est recommandé d'ajouter # type: ignore
pour empêcher les avertissements d'ajouter un attribut.
sur la base d'une réponse épinglée, J'ai écrit une petite méthode qui crée automatiquement de nouveaux niveaux
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.__= 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 ressembler à ça:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
Au lieu d’ajouter une méthode supplémentaire à la classe Logger, je vous 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, cela 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: racine: y2