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.

War es hilfreich?

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:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') Bedarf aufgerufen wird die neue Ebene intern erhalten registriert, so dass es beim Namen verwiesen werden kann.
  2. Die neuen Level-Anforderungen als Attribut hinzugefügt werden, selbst logging für Konsistenz. logging.TRACE = logging.DEBUG - 5
  3. Ein Verfahren namens trace Bedürfnisse der logging Modul hinzugefügt werden. Es sollte verhalten sich wie debug, info, etc.
  4. Ein Verfahren namens trace Anforderungen an die aktuell konfigurierten Logger Klasse hinzugefügt werden. Da dies nicht 100% garantiert logging.Logger, Verwendung logging.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:

  1. Verwenden Sie keine _log, Verwendung log (Sie müssen nicht isEnabledFor überprüfen)
  2. das Protokollierungsmodul sollte die eine Erstellung Instanz des benutzerdefinierten Logger sein, da es etwas Magie in getLogger tut, so dass Sie die Klasse über setLoggerClass einstellen müssen
  3. 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

erstellen
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 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top