Protokollieren variabler Daten mit neuer Formatzeichenfolge

84

Ich verwende die Protokollierungsfunktion für Python 2.7.3. Die Dokumentation für diese Python-Version lautet :

Das Protokollierungspaket datiert neuere Formatierungsoptionen wie str.format () und string.Template vor. Diese neueren Formatierungsoptionen werden unterstützt ...

Ich mag 'neues' Format mit geschweiften Klammern. Also versuche ich etwas zu tun wie:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

Und Fehler bekommen:

TypeError: Nicht alle Argumente wurden während der Formatierung der Zeichenfolge konvertiert

Was ich hier vermisse?

PS Ich möchte nicht verwenden

log.debug("format this message {0}".format(1))

In diesem Fall wird die Nachricht unabhängig von der Logger-Ebene immer formatiert.

MajesticRa
quelle
1
Sie können dies tun: log.debug("format this message%d" % 1)
Ronak
1
Sie müssen das konfigurieren Formatter, um '{' als Stil zu verwenden
mata
2
@ronak Danke für den Rat, aber nein. Bitte lesen Sie den Abschnitt "ps", warum. BTW log.debug ("formatiere diese Nachricht% d", 1) - funktioniert gut.
MajesticRa
@mata Wie konfiguriere ich es? Gibt es eine direkte Dokumentation dafür?
MajesticRa
@mata Ich habe es gefunden. Bitte machen Sie eine Antwort, damit ich sie als "richtige Antwort" festlegen kann. Nochmals
MajesticRa

Antworten:

38

BEARBEITEN: Sehen Sie sich den StyleAdapterAnsatz in der Antwort von @Dunes im Gegensatz zu dieser Antwort an. Es ermöglicht die Verwendung alternativer Formatierungsstile ohne Boilerplate, während die Methoden des Loggers (debug (), info (), error () usw.) aufgerufen werden.


Aus den Dokumenten - Verwendung alternativer Formatierungsstile :

Protokollierungsaufrufe (logger.debug (), logger.info () usw.) verwenden nur Positionsparameter für die eigentliche Protokollierungsnachricht selbst, wobei Schlüsselwortparameter nur zum Bestimmen von Optionen für die Behandlung des tatsächlichen Protokollierungsaufrufs verwendet werden (z. B. der Schlüsselwortparameter exc_info) um anzuzeigen, dass Traceback-Informationen protokolliert werden sollen, oder den zusätzlichen Schlüsselwortparameter, um zusätzliche Kontextinformationen anzugeben, die dem Protokoll hinzugefügt werden sollen). Daher können Sie Protokollierungsaufrufe nicht direkt mit der Syntax str.format () oder string.Template ausführen, da das Protokollierungspaket intern die Formatzeichenfolge und die Variablenargumente mit% -Formatierung zusammenführt. Dies würde unter Wahrung der Abwärtskompatibilität nicht geändert, da alle Protokollierungsaufrufe, die im vorhandenen Code vorhanden sind, Zeichenfolgen im% -Format verwenden.

Und:

Es gibt jedoch eine Möglichkeit, die Formatierung {} - und $ - zu verwenden, um Ihre individuellen Protokollnachrichten zu erstellen. Denken Sie daran, dass Sie für eine Nachricht ein beliebiges Objekt als Nachrichtenformatzeichenfolge verwenden können und dass das Protokollierungspaket str () für dieses Objekt aufruft, um die tatsächliche Formatzeichenfolge abzurufen.

Kopieren Sie dies in das whereverModul:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

Dann:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

Hinweis: Die eigentliche Formatierung wird verzögert, bis dies erforderlich ist. Wenn beispielsweise DEBUG-Nachrichten nicht protokolliert werden, wird die Formatierung überhaupt nicht durchgeführt.

jfs
quelle
4
Ab Python 3.6 können Sie F-Strings wie num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
folgt verwenden
11
@ P1h3r1e3d13 Im Gegensatz zum Protokollierungscode in der Antwort führen f '' - Zeichenfolgen die Formatierung sofort durch.
JFS
1
Richtig. Sie arbeiten hier, weil sie eine reguläre Zeichenfolge formatieren und zurückgeben, bevor sie die Protokollmethode aufrufen. Das kann für jemanden relevant sein oder auch nicht, daher denke ich, dass es als Option erwähnenswert ist.
Jacktose
3
@Jacktose Ich denke, Benutzer sollten sich nicht mit F-Strings anmelden, da dies die Protokollaggregationsdienste (z. B. Sentry) zunichte macht. Es gibt einen guten Grund, warum die stdlib-Protokollierung die Zeichenfolgenvorlage verzögert.
wim
31

Hier ist eine weitere Option, bei der die in der Antwort von Dunes genannten Keyword-Probleme nicht auftreten. Es können nur positional ( {0}) - Argumente und keine keyword ( {foo}) - Argumente verarbeitet werden. Es sind auch keine zwei Aufrufe zum Formatieren erforderlich (unter Verwendung des Unterstrichs). Es hat den ick-Faktor der Unterklasse str:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

Sie verwenden es so:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

Natürlich können Sie das mit gekennzeichnete Häkchen entfernen # optional, um alle Nachrichten über den Adapter zur Verwendung einer neuen Formatierung zu zwingen.


Hinweis für alle, die diese Antwort Jahre später lesen : Ab Python 3.2 können Sie den Stilparameter für FormatterObjekte verwenden:

Die Protokollierung (ab 3.2) bietet eine verbesserte Unterstützung für diese beiden zusätzlichen Formatierungsstile. Die Formatter-Klasse wurde um einen zusätzlichen optionalen Schlüsselwortparameter mit dem Namen erweitert style. Der Standardwert ist '%', aber andere mögliche Werte sind '{'und '$', die den beiden anderen Formatierungsstilen entsprechen. Die Abwärtskompatibilität wird standardmäßig beibehalten (wie erwartet). Wenn Sie jedoch explizit einen Stilparameter angeben, können Sie Formatzeichenfolgen angeben, die mit str.format()oder funktionieren string.Template.

Die Dokumente enthalten das Beispiel logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Beachten Sie, dass Sie in diesem Fall das loggermit dem neuen Format immer noch nicht aufrufen können . Das heißt, Folgendes wird immer noch nicht funktionieren:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either
Felipe
quelle
5
Ihre Aussage zu Python 3 ist falsch. Der Parameter style gilt nur für die Zeichenfolge im Formatierungsformat, nicht für die einzelnen Protokollnachrichten. Auf der Seite, auf die Sie verlinkt haben, heißt es ausdrücklich: "Dies würde unter Wahrung der Abwärtskompatibilität nicht geändert."
Mhsmith
1
Danke, dass du mich ehrlich hältst. Der erste Teil ist jetzt weniger nützlich, aber ich habe ihn in Bezug auf das umformuliert Formatter, was jetzt richtig ist (glaube ich). Das StyleAdapter funktioniert noch,
Felipe
@falstro - danke für den Hinweis. Die aktualisierte Version sollte jetzt funktionieren. Da BraceStringeine String - Unterklasse ist, dann ist es sicher selbst zurückzukehren , aus__str__
Felipe
1
antworte nur, dass style = "{", +1
Tom S.
24

Dies war meine Lösung für das Problem, als ich feststellte, dass bei der Protokollierung nur die Formatierung im printf-Stil verwendet wird. Dadurch können Protokollierungsaufrufe gleich bleiben - keine spezielle Syntax wie z log.info(__("val is {}", "x")). Die für den Code erforderliche Änderung besteht darin, den Logger in a zu verpacken StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

Verwendung ist:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

Es ist erwähnenswert, dass diese Umsetzungsprobleme haben , wenn Schlüsselwörter für Klammer Substitution verwendet wurden , umfassen level, msg, args, exc_info, extraoder stack_info. Dies sind Argumentnamen, die von der logMethode von verwendet werden Logger. Wenn Sie einen dieser Namen benötigen, ändern Sie diese process, um diese Namen auszuschließen, oder entfernen Sie sie einfach log_kwargsaus dem _logAnruf. Darüber hinaus ignoriert diese Implementierung auch stillschweigend falsch geschriebene Schlüsselwörter, die für den Logger bestimmt sind (z. B. ectra).

Dünen
quelle
4
Dieser Weg wird von Python Doc empfohlen, docs.python.org/3/howto/…
Eshizhan
23

Die einfachere Lösung wäre die Verwendung des hervorragenden logbookModuls

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

Oder umso vollständiger:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1
Thomas Orozco
quelle
Das sieht gut aus, aber gibt es eine Möglichkeit, Millisekunden statt nur Sekunden zu haben?
Jeff
@ Jeff sicher, mit Logbook können Sie benutzerdefinierte Handler mit benutzerdefinierten Zeichenfolgenformaten definieren und verwenden.
Thomas Orozco
5
@ Jeff Einige Jahre später - Die Standardzeitgenauigkeit beträgt Millisekunden.
Jan Vlcinsky
12

Wie in anderen Antworten erwähnt, wird die in Python 3.2 eingeführte Formatierung im geschweiften Stil nur für die Formatzeichenfolge verwendet, nicht für die eigentlichen Protokollnachrichten.

Um die Formatierung der eigentlichen Protokollnachricht im Klammerstil zu aktivieren, können wir einen Teil des Protokollierungscodes mit Affen-Patches versehen.

Im Folgenden wird das loggingModul gepatcht, um eine get_loggerFunktion zu erstellen , die einen Protokollierer zurückgibt, der die neue Formatierung für jeden von ihm verarbeiteten Protokolldatensatz verwendet.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

Verwendung:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

Anmerkungen:

  • Voll kompatibel mit normalen Logging - Methoden (nur ersetzen logging.getLoggermit get_logger)
  • Betrifft nur bestimmte von der get_loggerFunktion erstellte Logger (bricht keine Pakete von Drittanbietern).
  • Wenn bei einem normalen logging.getLogger()Aufruf erneut auf den Logger zugegriffen wird , gilt die Formatierung im neuen Stil weiterhin.
  • kwargs werden nicht unterstützt (macht es unmöglich , zu einem Konflikt mit der eingebauten in exc_info, stack_info, stacklevelund extra).
  • Der Leistungstreffer sollte minimal sein (Umschreiben eines einzelnen Funktionszeigers für jede Protokollnachricht).
  • Die Formatierung der Nachricht wird verzögert, bis sie ausgegeben wird (oder überhaupt nicht, wenn die Protokollnachricht gefiltert wird).
  • Args werden logging.LogRecordwie gewohnt auf Objekten gespeichert (in einigen Fällen nützlich mit benutzerdefinierten Protokollhandlern).
  • Wenn man sich den loggingQuellcode des Moduls ansieht , scheint es, als sollte er bis zur str.formatEinführung von Python 2.6 funktionieren (aber ich habe ihn nur in Version 3.5 und höher getestet).
pR0Ps
quelle
2
Die einzige Antwort, die berücksichtigt, dass die Debug-Zeichenfolge nur berechnet werden sollte, wenn die Debugger-Nachricht gedruckt werden soll. Vielen Dank!
Fafaman
2

Versuchen Sie es logging.setLogRecordFactoryin Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)
nexcvon
quelle
Es funktioniert, aber das Problem ist, dass Sie Module von Drittanbietern beschädigen, die die %Formatierung verwenden, da die Datensatzfactory für das Protokollierungsmodul global ist.
jtaylor
1

Ich habe einen benutzerdefinierten Formatierer namens ColorFormatter erstellt , der das Problem folgendermaßen behandelt:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

Dadurch bleibt es mit verschiedenen Bibliotheken kompatibel. Der Nachteil ist, dass es wahrscheinlich nicht performant ist, da möglicherweise zweimal versucht wird, die Zeichenfolge zu formatieren.

Gringo Suave
quelle
0

Hier ist etwas ganz Einfaches, das funktioniert:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

Dann:

mydebuglog("hello {} {val}", "Python", val="World")
Niederländische Meister
quelle
0

Ähnliche Lösung pR0Ps', Verpackung getMessagein LogRecorddurch Verpackung makeRecord(statt handlein ihrer Antwort) in Fällen , Loggerdass sollte neue Formatierungs-fähig sein:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

Ich habe dies mit Python 3.5.3 getestet.

Dragorn421
quelle