Ich hätte gerne Loglevel TRACE (5) für meine Bewerbung, da debug()
meiner Meinung nach nicht ausreicht. Außerdem ist log(5, msg)
nicht das, was ich will. Wie kann ich einem Python-Logger ein benutzerdefiniertes Loglevel hinzufügen?
Ich habe einen mylogger.py
mit folgendem Inhalt:
import logging
@property
def log(obj):
myLogger = logging.getLogger(obj.__class__.__name__)
return myLogger
In meinem Code verwende ich es folgendermaßen:
class ExampleClass(object):
from mylogger import log
def __init__(self):
'''The constructor with the logger'''
self.log.debug("Init runs")
Jetzt möchte ich self.log.trace("foo bar")
anrufen.
Vielen Dank im Voraus für Ihre Hilfe.
Edit (8. Dezember 2016): Ich habe die akzeptierte Antwort in pfa's geändert. Dies ist IMHO, eine ausgezeichnete Lösung, die auf dem sehr guten Vorschlag von Eric S basiert.
@Eric S.
Die Antwort von Eric S. ist hervorragend, aber ich habe durch Experimentieren gelernt, dass dies immer dazu führt, dass Nachrichten, die auf der neuen Debug-Ebene protokolliert wurden, gedruckt werden - unabhängig davon, auf welche Protokollebene sie eingestellt sind. Wenn Sie also eine neue Ebenennummer von 9 erstellen, wenn Sie setLevel (50) aufrufen, werden die Meldungen der unteren Ebene fälschlicherweise gedruckt. Um dies zu verhindern, benötigen Sie eine weitere Zeile in der Funktion "debugv", um zu prüfen, ob die betreffende Protokollierungsstufe tatsächlich aktiviert ist.
Behobenes Beispiel, das überprüft, ob die Protokollierungsstufe aktiviert ist:
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
Wenn Sie sich den Code für class Logger
in logging.__init__.py
für Python 2.7 ansehen, tun dies alle Standardprotokollfunktionen (.critical, .debug usw.).
Ich kann anscheinend keine Antworten auf die Antworten anderer posten, da er keinen guten Ruf hat ... hoffentlich wird Eric seinen Beitrag aktualisieren, wenn er das sieht. =)
Ich nahm die Antwort "Vermeiden Sie, Lambda zu sehen" und musste ändern, wo der log_at_my_log_level hinzugefügt wurde. Ich sah auch das Problem, das Paul tat: "Ich glaube nicht, dass das funktioniert. Benötigen Sie nicht Logger als erstes Argument in log_at_my_log_level?" Das hat bei mir funktioniert
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
Diese Frage ist ziemlich alt, aber ich habe mich gerade mit demselben Thema befasst und einen Weg gefunden, der den bereits erwähnten ähnlich ist, der mir ein wenig sauberer erscheint. Dies wurde mit 3.4 getestet. Ich bin mir nicht sicher, ob die verwendeten Methoden in älteren Versionen vorhanden sind:
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)
Wenn ich alle vorhandenen Antworten mit einer Reihe von Nutzungserfahrungen kombiniere, denke ich, dass ich eine Liste mit allen Dingen erstellt habe, die erforderlich sind, um eine vollständig nahtlose Verwendung der neuen Ebene zu gewährleisten. Die folgenden Schritte setzen voraus, dass Sie eine neue Ebene TRACE
mit dem Wert logging.DEBUG - 5 == 5
hinzufügen:
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
muss aufgerufen werden, damit die neue Ebene intern registriert wird, damit sie nach Name referenziert werden kann.logging
selbst als Attribut hinzugefügt werden: logging.TRACE = logging.DEBUG - 5
.trace
-Modul muss eine Methode namens logging
hinzugefügt werden. Es sollte sich genau wie debug
, info
usw. verhalten.trace
muss der aktuell konfigurierten Logger-Klasse hinzugefügt werden. Da dies nicht zu 100% logging.Logger
garantiert ist, verwenden Sie stattdessen logging.getLoggerClass()
.Alle Schritte werden in der folgenden Methode veranschaulicht:
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)
Wer hat mit der schlechten Praxis der Verwendung interner Methoden (self._log
) begonnen und warum basiert jede Antwort darauf ?! Die Pythonic-Lösung würde stattdessen self.log
verwenden, damit Sie sich nicht mit irgendwelchen internen Dingen herumschlagen müssen:
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')
Ich finde es einfacher, ein neues Attribut für das Logger-Objekt zu erstellen, das die Funktion log () übergibt. Ich denke, dass das Logger-Modul aus genau diesem Grund addLevelName () und log () enthält. Somit sind keine Unterklassen oder neue Methode erforderlich.
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
jetzt
mylogger.trace('This is a trace message')
sollte wie erwartet funktionieren.
Ich denke, Sie müssen die Klasse Logger
subclassieren und eine Methode namens trace
hinzufügen, die im Grunde Logger.log
mit einer niedrigeren Stufe als DEBUG
aufruft. Ich habe es nicht ausprobiert, aber dies ist, was die docs anzeigen.
Tipps zum Erstellen eines benutzerdefinierten Loggers:
_log
, verwenden Sie log
(Sie müssen isEnabledFor
nicht überprüfen)getLogger
etwas Magie ausübt. Daher müssen Sie die Klasse über setLoggerClass
festlegen.__init__
für den Logger, class, definieren, wenn Sie nichts speichern# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
def trace(self, msg, *args, **kwargs):
self.log(TRACE, msg, *args, **kwargs)
Wenn Sie diesen Logger aufrufen, verwenden Sie setLoggerClass(MyLogger)
, um ihn zum Standard-Logger von getLogger
zu machen.
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
Sie müssen setFormatter
, setHandler
und setLevel(TRACE)
in der handler
und in der log
selbst angeben, um diese Ablaufverfolgung auf niedriger Ebene tatsächlich zu sehen
Das hat für mich funktioniert:
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.')
Das Problem mit Lambda/funcName wurde mit logger._log behoben, wie @marqueed darauf hinweist. Ich denke, dass die Verwendung von Lambda etwas sauberer aussieht, aber der Nachteil ist, dass es keine Schlüsselwortargumente zulässt. Ich habe das selbst noch nie benutzt, also kein Biggie.
HINWEIS: Die Schule ist für den Sommer aus! Kumpel FATAL-Setup: Datei nicht gefunden .
Meiner Erfahrung nach ist dies die vollständige Lösung des Problems der Op ... um zu vermeiden, "Lambda" als Funktion zu sehen, in der die Nachricht ausgegeben wird, gehen Sie tiefer:
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
Ich habe noch nie versucht, mit einer eigenständigen Logger-Klasse zu arbeiten, aber ich denke, dass die Grundidee die gleiche ist (_log verwenden).
Ergänzung zu Mad Physicists Beispiel, um Dateinamen und Zeilennummer korrekt zu erhalten
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
Während wir bereits viele richtige Antworten haben, ist das Folgende meiner Meinung nach eher Pythonic:
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)
Wenn Sie mypy
in Ihrem Code verwenden möchten, wird empfohlen, # type: ignore
hinzuzufügen, um zu verhindern, dass Warnungen Attribute hinzufügen.
basierend auf festgehaltener antwort.... ich schrieb eine kleine methode, die automatisch neue logging-ebenen erstellt
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 kann smth so machen:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
Als Alternative zum Hinzufügen einer zusätzlichen Methode zur Logger-Klasse würde ich empfehlen, die Logger.log(level, msg)
-Methode zu verwenden.
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')
Ich bin verwirrt; Zumindest mit Python 3.5 funktioniert es einfach:
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")
ausgabe:
DEBUG: root: y1
TRACE: root: y2