Domanda

Mi piacerebbe avere livello di log TRACE (5) per la mia applicazione, come io non credo che sia sufficiente debug(). Inoltre non log(5, msg) è quello che voglio. Come posso aggiungere un livello di log personalizzato a un logger Python?

Ho un mylogger.py con il seguente contenuto:

import logging

@property
def log(obj):
    myLogger = logging.getLogger(obj.__class__.__name__)
    return myLogger

Nel mio codice lo uso nel seguente modo:

class ExampleClass(object):
    from mylogger import log

    def __init__(self):
        '''The constructor with the logger'''
        self.log.debug("Init runs")

Ora vorrei chiamare self.log.trace("foo bar")

Grazie in anticipo per il vostro aiuto.

Modifica (8 dicembre 2016): Ho cambiato la risposta accettata a di PFA che è , secondo me, un'ottima soluzione basata sul molto buona proposta Eric S.

È stato utile?

Soluzione

@Eric S.

La risposta di Eric S. è eccellente, ma ho imparato dalla sperimentazione che questo sarà sempre causa i messaggi registrati al nuovo livello di debug da stampare - indipendentemente da ciò che il livello di registrazione è impostato su. Quindi, se si effettua un nuovo numero livello di 9, se si chiama setLevel(50), la livello inferiore messaggi verranno erroneamente essere stampati.

  

Per evitare che ciò accada, è necessario un altro linea all'interno della funzione "debugv" per verificare se il livello di registrazione in questione è effettivamente attivato.

esempio fisso che controlla se il livello di registrazione è abilitata:

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

Se si guarda il codice per class Logger in logging.__init__.py per Python 2.7, questo è ciò che tutte le funzioni di protocollo standard di do (.critical, .debug, ecc.).

I apparentemente può non rispondere a quelle risposte degli altri per mancanza di reputazione ... si spera Eric aggiornerà il suo posto se vede questo. =)

Altri suggerimenti

Ho preso la risposta "evitare di vedere lambda" e dovuto modificare dove si stava aggiunto il log_at_my_log_level. Anch'io ho visto il problema che Paolo ha "Io non credo che questo funziona. Non hai bisogno Logger come il primo arg in log_at_my_log_level?" Questo ha funzionato per me

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

La combinazione di tutte le risposte esistenti con un mucchio di esperienza di utilizzo, penso che devo venire con un elenco di tutte le cose che hanno bisogno di essere fatto per garantire un uso completamente trasparente del nuovo livello. I passaggi riportati di seguito presuppongono che si sta aggiungendo un nuovo livello di TRACE con valore logging.DEBUG - 5 == 5:

  1. esigenze logging.addLevelName(logging.DEBUG - 5, 'TRACE') da invocare per ottenere il nuovo livello registrato internamente in modo che si può fare riferimento al nome.
  2. Le nuove esigenze di livello da aggiungere come attributo logging stessa di coerenza. logging.TRACE = logging.DEBUG - 5
  3. esigenze metodo A chiamato trace da aggiungere al modulo logging. Si dovrebbe comportarsi proprio come debug, info, ecc.
  4. esigenze metodo A chiamato trace da aggiungere alla classe logger attualmente configurato. Poiché questo non è garantita al 100% per essere logging.Logger, l'uso logging.getLoggerClass() invece.

Tutti i passaggi sono illustrati nella seguente metodo:

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)

Questa domanda è un po 'vecchia, ma ho appena affrontato lo stesso argomento e ha trovato un modo simile a quelli già citati, che appare un po' più pulito per me. Questo è stato testato su 3.4, quindi non sono sicuro se i metodi utilizzati esistono nelle versioni precedenti:

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)

Chi ha cominciato la cattiva prassi di utilizzare metodi interni (self._log) e perché è ogni risposta basato su quello ?! La soluzione divinatorio sarebbe quella di utilizzare self.log invece in modo da non dover pasticciare con qualsiasi roba interna:

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')

Trovo più facile creare un nuovo attributo per l'oggetto logger che passa la funzione log (). Credo che il modulo logger fornisce l'addLevelName () e il registro () per questo motivo. Così non sottoclassi o nuovo metodo necessari.

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

Ora

mylogger.trace('This is a trace message')

dovrebbe funzionare come previsto.

Credo che dovrete sottoclasse della classe Logger e aggiungere un metodo chiamato trace che chiama fondamentalmente Logger.log con un livello inferiore rispetto DEBUG. Non ho provato questo, ma questo è ciò che il documenti indicano .

Suggerimenti per la creazione di un logger personalizzato:

  1. Non utilizzare _log, uso log (non si controlla isEnabledFor)
  2. il modulo di registrazione dovrebbe essere quello di creare un'istanza del logger personalizzato dal momento che fa un po 'di magia nella getLogger, quindi sarà necessario impostare la classe tramite setLoggerClass
  3. Non è necessario definire __init__ per il logger, di classe se non si memorizzare nulla
# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
    def trace(self, msg, *args, **kwargs):
        self.log(TRACE, msg, *args, **kwargs)

Quando si chiama questo uso logger setLoggerClass(MyLogger) per rendere questo logger di default da getLogger

logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")

Sarà necessario setFormatter, setHandler, e setLevel(TRACE) sulla handler e sulla log stesso per SE in realtà questo basso livello di traccia

Questo ha funzionato per me:

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.')

Il problema lambda / funcName è fissato con logger._log come @marqueed sottolineato. Penso che usando lambda sembra un po 'più pulito, ma lo svantaggio è che essa non può prendere argomenti chiave. Non ho mai usato anch'io, quindi non disperatevi.

  NOTE     setup: school's out for summer! dude
  FATAL    setup: file not found.

Nella mia esperienza, questa è la soluzione completa del problema del op ... per evitare di vedere "lambda", come la funzione in cui viene emesso il messaggio, andare più a fondo:

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

Non ho mai provato a lavorare con una classe autonoma logger, ma penso che l'idea di base è lo stesso (uso _log).

L'aggiunta di Mad fisici esempio per ottenere il nome del file e il numero di riga corretto:

def logToRoot(message, *args, **kwargs):
    if logging.root.isEnabledFor(levelNum):
        logging.root._log(levelNum, message, args, **kwargs)

Mentre abbiamo già un sacco di risposte corrette, il seguente è a mio parere più divinatorio:

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)

Se si desidera utilizzare mypy sul vostro codice, si consiglia di aggiungere # type: ignore agli avvertimenti Soppressione di aggiungere attributi.

In alternativa all'aggiunta di un metodo in più per la classe Logger mi consiglia di utilizzare il metodo 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')

Sono confuso; con Python 3.5, per lo meno, funziona:

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")

uscita:

  

DEBUG: root: Y1

     

TRACE: root: y2

in base alla risposta Pinned, ho scritto un po 'di metodo che crea automaticamente nuovi livelli di registrazione

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 possono smth così:

new_log_levels = {
    # level_num is in logging.INFO section, that's why it 21, 22, etc..
    "FOO":      21,
    "BAR":      22,
}

Nel caso in cui qualcuno vuole un metodo automatico per aggiungere un nuovo livello di registrazione sul modulo di registrazione (o una copia di esso) in modo dinamico, ho creato questa funzione, in espansione @ risposta di 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top