Kann das Protokollierungsformat von Python abhängig von der Nachrichtenprotokollstufe geändert werden?

71

Ich verwende den Python- loggingMechanismus, um die Ausgabe auf dem Bildschirm zu drucken. Ich könnte dies mit print-Anweisungen tun, möchte dem Benutzer jedoch eine genauere Granularität ermöglichen, um bestimmte Ausgabetypen zu deaktivieren. Ich mag das Format, das für Fehler gedruckt wird, würde aber ein einfacheres Format bevorzugen, wenn der Ausgabepegel "info" ist.

Zum Beispiel:

  logger.error("Running cmd failed")
  logger.info("Running cmd passed")

In diesem Beispiel möchte ich, dass das Format des Fehlers anders gedruckt wird:

# error
Aug 27, 2009 - ERROR: Running cmd failed
# info
Running cmd passed

Ist es möglich, unterschiedliche Formate für unterschiedliche Protokollebenen zu haben, ohne mehrere Protokollierungsobjekte zu haben? Ich würde es vorziehen, dies zu tun, ohne den Logger nach seiner Erstellung zu ändern, da es eine große Anzahl von if / else-Anweisungen gibt, um zu bestimmen, wie die Ausgabe protokolliert werden soll.

Bedwyr
quelle

Antworten:

32

Ja, Sie können dies tun, indem Sie eine benutzerdefinierte FormatterKlasse haben:

class MyFormatter(logging.Formatter):
    def format(self, record):
        #compute s according to record.levelno
        #for example, by setting self._fmt
        #according to the levelno, then calling
        #the superclass to do the actual formatting
        return s

Fügen Sie dann eine MyFormatterInstanz Ihren Handlern hinzu.

Vinay Sajip
quelle
Hervorragend - das hat perfekt funktioniert. Ich habe die format () -Methode geändert, um levelno zu überprüfen und die Nachricht bei Bedarf zu ändern. Andernfalls wird es auf die ursprüngliche Zeichenfolge zurückgesetzt, die ich übergeben habe. Danke!
Bedwyr
9
Bitte entfernen Sie das Häkchen aus dieser Antwort. Der direkt darunter ist vollständig. In dieser Antwort fehlen große Codebereiche, in denen nur Kommentare enthalten sind, die beschreiben, was Sie tun sollten .
Utkonos
78

Ich bin gerade auf dieses Problem gestoßen und hatte Probleme, die im obigen Beispiel verbleibenden "Löcher" auszufüllen. Hier ist eine vollständigere, funktionierende Version, die ich verwendet habe. Hoffentlich hilft das jemandem:

# Custom formatter
class MyFormatter(logging.Formatter):

    err_fmt  = "ERROR: %(msg)s"
    dbg_fmt  = "DBG: %(module)s: %(lineno)d: %(msg)s"
    info_fmt = "%(msg)s"


    def __init__(self, fmt="%(levelno)s: %(msg)s"):
        logging.Formatter.__init__(self, fmt)


    def format(self, record):

        # Save the original format configured by the user
        # when the logger formatter was instantiated
        format_orig = self._fmt

        # Replace the original format with one customized by logging level
        if record.levelno == logging.DEBUG:
            self._fmt = MyFormatter.dbg_fmt

        elif record.levelno == logging.INFO:
            self._fmt = MyFormatter.info_fmt

        elif record.levelno == logging.ERROR:
            self._fmt = MyFormatter.err_fmt

        # Call the original formatter class to do the grunt work
        result = logging.Formatter.format(self, record)

        # Restore the original format configured by the user
        self._fmt = format_orig

        return result

Bearbeiten:

Komplimente von Halloleo, hier ist ein Beispiel, wie Sie das Obige in Ihrem Skript verwenden können:

fmt = MyFormatter()
hdlr = logging.StreamHandler(sys.stdout)

hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
logging.root.setLevel(DEBUG)

Bearbeiten 2:

Die Python3-Protokollierung hat sich etwas geändert. Sehen Sie hier für einen Python3 Ansatz.

JS.
quelle
Das funktioniert super! Ich habe die Verweise auf " MyFormatterName" geändert, um die selfKonsistenz zu erhöhen.
Halloleo
2
Und hier möchte ich hinzufügen, wie Sie die MyFormatter-Klasse in Ihrem Programm verwenden können (ersetzen Sie jeden <CR> durch einen Wagenrücklauf): fmt = MyFormatter()<CR> hdlr = logging.StreamHandler(sys.stdout)<CR> <CR> hdlr.setFormatter (fmt) <CR> logging.root.addHandler (hdlr) <CR> logging. root.setLevel (DEBUG) `<CR>
halloleo
6
Diese Antwort funktioniert nach 3.2 aufgrund der Änderungen im internen Protokollierungsmechanismus nicht mehr. logging.Formatter.formathängt jetzt vom styleParameter von ab __init__.
Evpok
2
Evpok hat recht. Fügen Sie dies hinzu, nachdem Sie self._fmt zugewiesen haben:self._style = logging.PercentStyle(self._fmt)
Ross R
2
Könnte dies verbessert werden, indem verwendet super()statt aufgerufen wird logging.Formatter?
Phoenix
16

Und wieder wie JS antworten, aber kompakter.

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG :"DBG: %(module)s: %(lineno)d: %(message)s",
               logging.ERROR : "ERROR: %(message)s",
               logging.INFO : "%(message)s",
               'DEFAULT' : "%(levelname)s: %(message)s"}

    def format(self, record):
        self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
estani
quelle
2
Diese Antwort funktioniert nach 3.2 aufgrund der Änderungen im internen Protokollierungsmechanismus nicht mehr. logging.Formatter.formathängt jetzt vom styleParameter von ab __init__.
Evpok
10

Dies ist eine Anpassung von Estanis Antwort auf die neue Implementierung, logging.Formatterdie nun auf Formatierungsstilen beruht. Meins basiert auf dem '{'Stilformat, kann aber angepasst werden. Könnte verfeinert werden, um allgemeiner zu sein und die Auswahl des Formatierungsstils und der benutzerdefinierten Nachrichten als Argumente __init__zu ermöglichen.

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),
           logging.ERROR : logging._STYLES['{']("{module} ERROR: {message}"),
           logging.INFO : logging._STYLES['{']("{module}: {message}"),
           'DEFAULT' : logging._STYLES['{']("{module}: {message}")}

    def format(self, record):
        # Ugly. Should be better
        self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
Evpok
quelle
1
Vielen Dank, dass Sie dies aktualisiert haben, um mit Python3 zu arbeiten. Ich bin in Python3 auf dasselbe Problem gestoßen und habe eine ähnliche Lösung gefunden. Würden Sie bitte so freundlich sein, diese Antwort auch dort zu posten? stackoverflow.com/questions/14844970/…
JS.
2
Verwenden Sie den neuen '{' Stil in Ihren Kommentaren? :-)
JS.
10

Anstatt sich auf Stile oder interne Felder zu verlassen, können Sie auch einen Formatierer erstellen, der abhängig von record.levelno (oder anderen Kriterien) an andere Formatierer delegiert. Dies ist meiner bescheidenen Meinung nach eine etwas sauberere Lösung. Der folgende Code sollte für jede Python-Version> = 2.7 funktionieren:

Der einfache Weg würde ungefähr so ​​aussehen:

class MyFormatter(logging.Formatter):

    default_fmt = logging.Formatter('%(levelname)s in %(name)s: %(message)s')
    info_fmt = logging.Formatter('%(message)s')

    def format(self, record):
        if record.levelno == logging.INFO:
            return self.info_fmt.format(record)
        else:
            return self.default_fmt.format(record)

Aber Sie könnten es allgemeiner machen:

class VarFormatter(logging.Formatter):

    default_formatter = logging.Formatter('%(levelname)s in %(name)s: %(message)s')

    def __init__(self, formats):
        """ formats is a dict { loglevel : logformat } """
        self.formatters = {}
        for loglevel in formats:
            self.formatters[loglevel] = logging.Formatter(formats[loglevel])

    def format(self, record):
        formatter = self.formatters.get(record.levelno, self.default_formatter)
        return formatter.format(record)

Ich habe hier ein Diktat als Eingabe verwendet, aber natürlich können Sie auch Tupel verwenden, ** kwargs, was auch immer Ihr Boot schwimmt. Dies würde dann verwendet werden wie:

formatter = VarFormatter({logging.INFO: '[%(message)s]', 
                          logging.WARNING: 'warning: %(message)s'})
<... attach formatter to logger ...>
Joris
quelle
9

EIN WEG, DAS ZU TUN

Definieren Sie eine Klasse

import logging

class CustomFormatter(logging.Formatter):
    """Logging Formatter to add colors and count warning / errors"""

    grey = "\x1b[38;21m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

Logger instanziieren

# create logger with 'spam_application'
logger = logging.getLogger("My_app")
logger.setLevel(logging.DEBUG)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

ch.setFormatter(CustomFormatter())

logger.addHandler(ch)

Und benutze!

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

Ergebnis Geben Sie hier die Bildbeschreibung ein

Sergey Pleshakov
quelle
1
Dies scheint die einzige Antwort zu sein, die an Python 3.6
user3821178
1
Ich versuche, die gleiche Antwort hier auf dem neuesten Stand zu halten. Stackoverflow.com/a/56944256/9150146 , wenn Hilfe hilft, bitte abstimmen. Vielen Dank @ user3821178
Sergey Pleshakov
Dies ist eine ausgezeichnete Antwort. Die Protokollierung der Farbcodierung ist ebenfalls sehr hilfreich.
Akash Desarda
6

Die obige Lösung funktioniert mit Version 3.3.3. Mit 3.3.4 erhalten Sie jedoch den folgenden Fehler.

FORMATS = { logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),

TypeError: Das 'Tupel'-Objekt kann nicht aufgerufen werden

Nach einigem Suchen in der Protokollierungsklasse Lib \ logging__init __. Py stellte ich fest, dass sich eine Datenstruktur von 3.3.3 auf 3.3.4 geändert hat, die das Problem verursacht

3.3.3

_STYLES = {
    '%': PercentStyle,
    '{': StrFormatStyle,
    '$': StringTemplateStyle
}

3.3.4

_STYLES = {
   '%': (PercentStyle, BASIC_FORMAT),
   '{': (StrFormatStyle, '{levelname}:{name}:{message} AA'),
    '$': (StringTemplateStyle, '${levelname}:${name}:${message} BB'),
}

Die aktualisierte Lösung ist daher

class SpecialFormatter(logging.Formatter):
     FORMATS = {logging.DEBUG : logging._STYLES['{'][0]("{module} DEBUG: {lineno}: {message}"),
       logging.ERROR : logging._STYLES['{'][0]("{module} ERROR: {message}"),
       logging.INFO : logging._STYLES['{'][0]("{module}: {message}"),
       'DEFAULT' : logging._STYLES['{'][0]("{module}: {message}")}

 def format(self, record):
    # Ugly. Should be better
    self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
    return logging.Formatter.format(self, record)
user1837990
quelle
Es kann einfacher sein, den from logging import StrFormatStylelogging._STYLES['{'][0]
Stiltyp
3

Wenn Sie nur die Formatierung bestimmter Ebenen überspringen möchten, können Sie etwas Einfacheres als die anderen Antworten wie die folgenden tun:

class FormatterNotFormattingInfo(logging.Formatter):
    def __init__(self, fmt = '%(levelname)s:%(message)s'):
        logging.Formatter.__init__(self, fmt)

    def format(self, record):
        if record.levelno == logging.INFO:
            return record.getMessage()
        return logging.Formatter.format(self, record)

Dies hat auch den Vorteil, dass vor und nach der Version 3.2 gearbeitet wird, indem keine internen Variablen wie self._fmt oder self._style verwendet werden.

JDiMatteo
quelle
Ich denke, das ist die sauberste Lösung
vlk