Wie eine benutzerdefinierte loglevel zu Pythons Protokollierungsfunktion hinzufügen
-
24-09-2019 - |
Frage
Ich möchte loglevel TRACE (5) für meine Anwendung haben, da ich nicht glaube, dass debug()
ausreichend ist. Zusätzlich ist log(5, msg)
nicht das, was ich will. Wie kann ich eine benutzerdefinierte loglevel zu einem Python-Logger hinzufügen?
Ich habe eine mylogger.py
mit folgendem Inhalt:
import logging
@property
def log(obj):
myLogger = logging.getLogger(obj.__class__.__name__)
return myLogger
In meinem Code ich es in der folgenden Art und Weise verwendet werden:
class ExampleClass(object):
from mylogger import log
def __init__(self):
'''The constructor with the logger'''
self.log.debug("Init runs")
Nun möchte ich nennen möchte self.log.trace("foo bar")
Vielen Dank im Voraus für Ihre Hilfe.
Bearbeiten (8. Dezember 2016): Ich änderte die akzeptierte Antwort auf pfa der das ist IMHO, eine ausgezeichnete Lösung basiert auf dem sehr guten Vorschlag von Eric S.
Lösung
@Eric S.
Eric S. Antwort ist ausgezeichnet, aber ich lernte durch Experimente, dass dies immer Ursache Nachrichten auf dem neuen Debug-Level angemeldet gedruckt werden - unabhängig davon, was die Protokollebene eingestellt ist. Also, wenn Sie eine neue Ebene Anzahl der 9
machen, wenn Sie setLevel(50)
nennen, die untere Ebene Nachrichten fälschlicherweise gedruckt werden.
Um das zu verhindern, müssen Sie eine andere Zeile in der „debugv“ Funktion überprüfen, ob die Protokollierungsebene in Frage tatsächlich aktiviert ist.
Fixed Beispiel, dass überprüft, ob die Protokollierungsebene 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 den Code für class Logger
in logging.__init__.py
für Python aussehen 2.7, das ist, was alle Standard-Protokollfunktionen tun (.critical, .debug usw.).
Ich kann offenbar nicht Beitrag Antworten auf andere Antworten des Mangels an guten Ruf ... hoffentlich wird Eric seinen Posten aktualisieren, wenn er das sieht. =)
Andere Tipps
Ich habe die „avoid sehen Lambda“ Antwort und hatte zu ändern, wo die log_at_my_log_level hinzugefügt wurde. Ich sah auch das Problem, dass Paulus hat „Ich habe diese Werke nicht denken. Sie nicht als erste arg in log_at_my_log_level Logger?“ Das ist für mich gearbeitet
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
all vorhandenen Antworten mit einem Bündel von Nutzungs Erfahrung kombinierend, denke ich, dass ich mit einer Liste aller Dinge einfallen lassen, die getan werden müssen, um vollständig nahtlose Nutzung der neuen Ebene zu gewährleisten. Die folgenden Schritte setzen voraus, dass Sie eine neue Ebene TRACE
mit Wert logging.DEBUG - 5 == 5
hinzufügen:
-
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
Bedarf aufgerufen wird die neue Ebene intern erhalten registriert, so dass es beim Namen verwiesen werden kann. - Die neuen Level-Anforderungen als Attribut hinzugefügt werden, selbst
logging
für Konsistenz.logging.TRACE = logging.DEBUG - 5
- Ein Verfahren namens
trace
Bedürfnisse derlogging
Modul hinzugefügt werden. Es sollte verhalten sich wiedebug
,info
, etc. - Ein Verfahren namens
trace
Anforderungen an die aktuell konfigurierten Logger Klasse hinzugefügt werden. Da dies nicht 100% garantiertlogging.Logger
, Verwendunglogging.getLoggerClass()
statt sein.
Alle Schritte werden bei dem Verfahren unten dargestellt:
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)
Diese Frage ist ziemlich alt, aber ich behandeln nur mit demselben Thema und fand einen Weg, ähnlich wie die bereits erwähnte, die mir ein wenig sauberer erscheint. Dies wurde auf 3.4 getestet, so dass ich bin mir nicht sicher, ob die Methoden gibt es in älteren Versionen verwendet:
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)
Wer hat die schlechte Praxis der Verwendung von internen Verfahren gestartet (self._log
) und warum jede Antwort darauf basiert ?! Die pythonic Lösung Gebrauch self.log
wäre statt, so dass Sie nicht mit einer internen Sache zu verwirren haben:
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 den Logger-Objekt zu erstellen, das die Funktion log () übergibt. Ich denke, dass das Logger-Modul die addLevelName () und das Protokoll () aus diesem Grunde bietet. So benötigt keine Unterklassen oder eine neue Methode.
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.
Ich denke, Sie werden die Logger
Klasse, Unterklasse und eine Methode namens trace
hinzufügen, die im Grunde Logger.log
mit einem Pegel nennt als DEBUG
senken. Ich habe nicht versucht, aber das ist, was der docs anzuzeigen.
Tipps für eine benutzerdefinierte Logger zu erstellen:
- Verwenden Sie keine
_log
, Verwendunglog
(Sie müssen nichtisEnabledFor
überprüfen) - das Protokollierungsmodul sollte die eine Erstellung Instanz des benutzerdefinierten Logger sein, da es etwas Magie in
getLogger
tut, so dass Sie die Klasse übersetLoggerClass
einstellen müssen - Sie brauchen nicht
__init__
für den Logger, Klasse zu definieren, wenn Sie nichts zu 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)
Beim Aufruf dieser Logger Verwendung setLoggerClass(MyLogger)
dies die Standard-Logger von getLogger
machen
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
Sie werden auf setFormatter
benötigen, setHandler
und setLevel(TRACE)
auf dem handler
und auf dem log
selbst diese geringe Spur tatsächlich se
Das ist für mich gearbeitet:
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 Lambda / funcName Problem wird mit logger._log fixiert, wie @marqueed hingewiesen. Ich denke, mit Lambda sieht ein bisschen saubere, aber der Nachteil ist, dass es nicht Keyword-Argumente nehmen. Ich habe noch nie, dass ich verwendet, also kein großes Problem.
NOTE setup: school's out for summer! dude FATAL setup: file not found.
Nach meiner Erfahrung ist dies die vollständige Lösung des das Problem des op ... sehen „Lambda“, wie die Funktion zu vermeiden, in der die Nachricht ausgesendet wird, gehe 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 Arbeit nie versucht, mit einer Standalone-Logger-Klasse, aber ich denke, die Grundidee ist die gleiche (Verwendung _log).
Zusatz Mad Physiker Beispiel Dateinamen zu erhalten und die Zeilennummer korrekt:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
Während wir schon viele richtige Antworten haben, ist die folgende meiner Meinung nach mehr 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
auf Ihrem Code verwenden möchten, empfiehlt es sich aus der Addition Attribut # type: ignore
zu Unterdrückungs-Warnungen hinzuzufügen.
Als Alternative eine zusätzliche Methode, um die Logger-Klasse zum Hinzufügen möchte ich mit der Logger.log(level, msg)
Methode empfehlen.
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; mit Python 3.5, zumindest, es funktioniert 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
basierend auf gemerkt Antwort, Ich schrieb eine wenig Methode, die automatisch neue Protokollebene
erstellendef 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 kann wie das smth:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
Falls jemand will eine automatisierte Art und Weise eine neue Protokollebene auf das Erfassungsmodul (oder eine Kopie davon) dynamisch hinzuzufügen, ich diese Funktion erstellt habe, erweitern @ pfa Antwort:
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