Cómo agregar un nivel de registro personalizado para facilidad de registro de Python
-
24-09-2019 - |
Pregunta
Me gustaría tener TRACE nivel de registro (5) para mi aplicación, ya que no creo que debug()
es suficiente. Además log(5, msg)
no es lo que quiero. ¿Cómo puedo añadir un nivel de registro personalizado a un registrador de Python?
Tengo un mylogger.py
con el siguiente contenido:
import logging
@property
def log(obj):
myLogger = logging.getLogger(obj.__class__.__name__)
return myLogger
En mi código lo uso de la siguiente manera:
class ExampleClass(object):
from mylogger import log
def __init__(self):
'''The constructor with the logger'''
self.log.debug("Init runs")
Ahora me gustaría llamar self.log.trace("foo bar")
Gracias de antemano por su ayuda.
Editar (dic octavo 2016): He cambiado la respuesta aceptada para de PFA que es , en mi humilde opinión, una excelente solución basada en la muy buena propuesta de Eric S.
Solución
@Eric S.
La respuesta de Eric S. es excelente, pero he aprendido mediante la experimentación que esto siempre causa mensajes registrados en el nuevo nivel de depuración que se desea imprimir - independientemente de lo que el nivel de registro se establece en. Así que si usted hace un nuevo número de nivel de 9
, si se llama a setLevel(50)
, la nivel inferior se pueden imprimir mensajes erróneamente.
Para evitar que esto ocurra, se necesita otra línea dentro de la función "debugv" para comprobar si el nivel de registro en cuestión está realmente activado.
ejemplo fijo que comprueba si el nivel de registro está habilitado:
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 nos fijamos en el código para class Logger
en logging.__init__.py
para Python 2.7, esto es lo que todas las funciones de registro estándar hacen (.critical, .debug, etc.).
Me aparentemente no puede publicar las respuestas a las respuestas de otros por falta de reputación ... esperemos Eric actualizará su puesto si ve esto. =)
Otros consejos
Me tomó la respuesta "no ver lambda" y tuvo que modificar en el que se están añadiendo las log_at_my_log_level. Yo también vi el problema que Pablo "no creo que esto funciona. ¿Usted no necesita registrador como el primer argumento en log_at_my_log_level?" Esto funcionó para mí
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
Combinar todas las respuestas existentes con un montón de experiencia de uso, creo que han llegado con una lista de todas las cosas que hay que hacer para garantizar un uso completamente transparente del nuevo nivel. Los pasos siguientes se supone que va a agregar un nuevo nivel con el valor TRACE
logging.DEBUG - 5 == 5
:
- necesidades
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
a ser invocados para obtener el nuevo nivel registrado internamente de modo que se pueda hacer referencia por su nombre. - Las nuevas necesidades de nivel que se añaden como un atributo para
logging
sí mismo para mantener la coherencia:.logging.TRACE = logging.DEBUG - 5
- necesidades método A llamado
trace
que se añade al módulo delogging
. Se debe comportarse igual quedebug
,info
, etc. - Un método necesidades llamada
trace
que se añade a la clase registrador configurado actualmente. Como esto no es 100% garantizado para serlogging.Logger
, el usologging.getLoggerClass()
lugar.
Todas las etapas se ilustran en el método a continuación:
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)
Esta pregunta es bastante antiguo, pero simplemente trató el mismo tema y encontró una manera similar a los ya mencionados, que parece un poco más limpio para mí. Esto fue probado en 3.4, así que no estoy seguro de si los métodos utilizados existen en versiones anteriores:
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én comenzó la mala práctica de utilizar métodos internos (self._log
) y por qué es cada una respuesta basada en eso ?! La solución Pythonic sería el uso de self.log
lugar por lo que no tiene que meterse con cualquier material interno:
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')
Me resulta más fácil crear un nuevo atributo para el objeto registrador que pasa a la función log (). Creo que el módulo registrador proporciona la addLevelName () y el registro () por esta misma razón. Por lo tanto no necesitan subclases o nuevo método.
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
ahora
mylogger.trace('This is a trace message')
debería funcionar como se espera.
Creo que tendrá que subclase de la clase Logger
y añadir un método llamado trace
que pide básicamente Logger.log
con un nivel más bajo que DEBUG
. No he probado esto, pero esto es lo que los documentos indican .
Consejos para crear un registrador de encargo:
- No utilice
_log
, el usolog
(usted no tiene que comprobarisEnabledFor
) - del módulo de registro debe ser la creación de una instancia del registrador de costumbre, ya que hace un poco de magia en
getLogger
, por lo que tendrá que establecer la clase a través desetLoggerClass
- Usted no necesita definir
__init__
para el registrador, clase si no está almacenando nada
# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
def trace(self, msg, *args, **kwargs):
self.log(TRACE, msg, *args, **kwargs)
Cuando se llama a este uso registrador setLoggerClass(MyLogger)
para hacer de este el registrador predeterminado de getLogger
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
tendrá que setFormatter
, setHandler
y setLevel(TRACE)
en el handler
y en el propio log
a SE realidad esta traza bajo nivel
Esto funcionó para mí:
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 cuestión lambda / Nombre de función se fija con logger._log como @marqueed señaló. Creo que usando lambda se ve un poco más limpia, pero el inconveniente es que no puede tomar argumentos de palabras clave. Nunca he utilizado yo mismo, por lo que no es problema.
NOTE setup: school's out for summer! dude FATAL setup: file not found.
En mi experiencia, esta es la solución completa al problema de la op ... para no ver "lambda" como la función en la que se emite el mensaje, ir más profundo:
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
Nunca lo he intentado trabajar con una clase independiente registrador, pero creo que la idea básica es la misma (uso _log).
La adición al ejemplo Mad físicos para obtener el nombre del archivo y el número de línea correcta:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
Si bien tenemos ya un montón de respuestas correctas, lo que sigue es en mi opinión más 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)
Si desea utilizar mypy
en su código, se recomienda añadir # type: ignore
a las advertencias a suprimir de la adición de atributos.
Como alternativa a la adición de un método adicional para la clase Logger Yo recomiendo usar el método 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')
Estoy confundido; con Python 3.5, al menos, simplemente funciona:
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")
salida:
DEBUG: root: y1
TRACE: root: y2
basado en la respuesta Pinned, Escribí un pequeño método que crean de forma automática nuevos niveles de registro
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 puede smth así:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
En caso de que alguien quiere una forma automatizada de añadir un nuevo nivel de registro en el módulo de registro (o una copia de la misma) de forma dinámica, he creado esta función, la ampliación de la respuesta 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