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.

¿Fue útil?

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:

  1. 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.
  2. 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
  3. necesidades método A llamado trace que se añade al módulo de logging. Se debe comportarse igual que debug, info, etc.
  4. Un método necesidades llamada trace que se añade a la clase registrador configurado actualmente. Como esto no es 100% garantizado para ser logging.Logger, el uso logging.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:

  1. No utilice _log, el uso log (usted no tiene que comprobar isEnabledFor)
  2. 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 de setLoggerClass
  3. 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top