Rejestrowanie danych zmiennych z nowym ciągiem formatu

85

Używam narzędzia do logowania w Pythonie 2.7.3. Dokumentacja dla tej wersji Pythona mówi :

pakiet rejestrowania poprzedza nowsze opcje formatowania, takie jak str.format () i string.Template. Te nowsze opcje formatowania są obsługiwane ...

Podoba mi się „nowy” format z nawiasami klamrowymi. Więc próbuję zrobić coś takiego:

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

I otrzymaj błąd:

TypeError: nie wszystkie argumenty zostały przekonwertowane podczas formatowania ciągu

Za czym tęsknię?

PS nie chcę używać

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

ponieważ w tym przypadku wiadomość jest zawsze formatowana niezależnie od poziomu rejestratora.

MajesticRa
źródło
1
Możesz to zrobić: log.debug("format this message%d" % 1)
ronak
1
musisz skonfigurować Formatterużywanie '{' jako stylu
mata
2
@ronak Dzięki za radę, ale nie. Zobacz sekcję "ps" dlaczego. BTW log.debug ("sformatuj tę wiadomość% d", 1) - działa dobrze.
MajesticRa
@mata Jak to skonfigurować? Czy istnieje bezpośrednia dokumentacja tego?
MajesticRa
@mata znalazłem to. Proszę, podaj odpowiedź, abym mógł ustawić ją jako „właściwą odpowiedź. Jeszcze raz dziękuję.
MajesticRa

Odpowiedzi:

38

EDYCJA: spójrz na StyleAdapterpodejście w odpowiedzi @Dunes w przeciwieństwie do tej odpowiedzi; pozwala na użycie alternatywnych stylów formatowania bez standardowego podczas wywoływania metod rejestratora (debug (), info (), error (), itp.).


Z dokumentów - Stosowanie alternatywnych stylów formatowania :

Rejestrowanie wywołań (logger.debug (), logger.info () itd.) Przyjmuje parametry pozycyjne tylko dla samego komunikatu logowania, z parametrami słów kluczowych używanymi tylko do określania opcji obsługi rzeczywistego wywołania logowania (np. aby wskazać, że informacje śledzenia powinny być rejestrowane lub dodatkowe słowo kluczowe, aby wskazać dodatkowe informacje kontekstowe, które mają zostać dodane do dziennika). Nie można więc bezpośrednio wykonywać wywołań rejestrowania za pomocą składni str.format () lub string.Template, ponieważ wewnętrznie pakiet rejestrowania używa% -formatting do scalenia ciągu formatu i argumentów zmiennych. Nie zmieniłoby się tego przy zachowaniu kompatybilności wstecznej, ponieważ wszystkie wywołania rejestrowania, które istnieją w istniejącym kodzie, będą używać ciągów% -format.

I:

Istnieje jednak sposób, w jaki można użyć formatowania {} i $ - do konstruowania indywidualnych komunikatów dziennika. Przypomnij sobie, że w przypadku wiadomości możesz użyć dowolnego obiektu jako ciągu formatu wiadomości i że pakiet rejestrowania wywoła str () na tym obiekcie, aby uzyskać rzeczywisty ciąg formatu.

Skopiuj i wklej to do wherevermodułu:

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)

Następnie:

from wherever import BraceMessage as __

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

Uwaga: faktyczne formatowanie jest opóźnione do momentu, gdy jest to konieczne, np. Jeśli komunikaty DEBUG nie są rejestrowane, formatowanie nie jest w ogóle wykonywane.

jfs
źródło
4
Począwszy od Pythona 3.6, możesz używać f-stringów w następujący sposób:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose
11
@ P1h3r1e3d13 w przeciwieństwie do kodu logowania w odpowiedzi, f '' - ciągi znaków wykonują formatowanie natychmiast.
jfs
1
Dobrze. Działają tutaj, ponieważ formatują i zwracają zwykły ciąg przed wywołaniem metody dziennika. To może być dla kogoś istotne lub nie, więc myślę, że warto o tym wspomnieć jako o opcji.
Jacktose
4
@Jacktose Myślę, że użytkownicy nie powinni logować się przy użyciu f-stringów, to omija usługi agregacji logów (np. Wartownik). Istnieje dobry powód, dla którego rejestrowanie w standardowym standardzie odracza tworzenie szablonów ciągu.
wim
31

Oto kolejna opcja, która nie ma problemów ze słowami kluczowymi wymienionymi w odpowiedzi Dunes. Obsługuje tylko {0}argumenty pozycyjne ( ), a nie argumenty słów kluczowych ( {foo}). Nie wymaga również dwóch wywołań formatowania (przy użyciu podkreślenia). Ma ten współczynnik podklasy 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

Używasz tego w ten sposób:

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

Oczywiście można usunąć zaznaczenie oznaczone ikoną, # optionalaby wymusić na wszystkich komunikatach przechodzących przez adapter użycie nowego stylu formatowania.


Uwaga dla każdego, kto po latach przeczyta tę odpowiedź : Począwszy od Pythona 3.2 , można używać parametru style z Formatterobiektami:

Rejestrowanie (od 3.2) zapewnia ulepszoną obsługę tych dwóch dodatkowych stylów formatowania. Klasa Formatter została rozszerzona o dodatkowy, opcjonalny parametr słowa kluczowego o nazwie style. Domyślnie jest to '%', ale inne możliwe wartości to '{'i '$', które odpowiadają dwóm pozostałym stylom formatowania. Zgodność wsteczna jest utrzymywana domyślnie (zgodnie z oczekiwaniami), ale poprzez jawne określenie parametru stylu uzyskuje się możliwość określenia ciągów formatu, które działają z str.format()lub string.Template.

Dokumentacja dostarcza przykładu logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Zauważ, że w tym przypadku nadal nie możesz zadzwonić loggerdo nowego formatu. To znaczy, że nadal nie będzie działać:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either
Felipe
źródło
6
Twoje stwierdzenie dotyczące Pythona 3 jest nieprawidłowe. Parametr style ma zastosowanie tylko do ciągu formatu programu Formatter, a nie do poszczególnych komunikatów dziennika. Strona, do której utworzyłeś łącze, zawiera komunikat: „Nie można tego zmienić przy zachowaniu wstecznej zgodności”.
mhsmith
1
Dzięki za zachowanie mnie uczciwości. Pierwsza część jest teraz mniej przydatna, ale przeformułowałem ją pod kątem Formatter, co jest teraz poprawne (myślę). Te StyleAdapter prace nadal,
Felipe
@falstro - dzięki za zwrócenie uwagi. Zaktualizowana wersja powinna teraz działać. Ponieważ BraceStringjest to podklasa typu string, można bezpiecznie wrócić z__str__
Felipe
1
tylko odpowiedź, która wymienia style = "{", +1
Tom S.
24

To było moje rozwiązanie problemu, gdy zauważyłem, że rejestrowanie wykorzystuje tylko formatowanie w stylu printf. Pozwala to rejestrować wywołania, które pozostają takie same - bez specjalnej składni, takiej jak log.info(__("val is {}", "x")). Zmiana wymagana do kodu polega na zawinięciu rejestratora w plik 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}

Wykorzystanie to:

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

Warto zauważyć, że ta implementacja ma problemy, jeśli słowa kluczowe używane do zastąpienia nawiasów to level, msg, args, exc_info, extralub stack_info. Są to nazwy argumentów używane przez logmetodę Logger. Jeśli potrzebujesz jednej z tych nazw, zmień je, processaby wykluczyć te nazwy lub po prostu usuń je log_kwargsz _logpołączenia. Co więcej, ta implementacja również po cichu ignoruje błędnie napisane słowa kluczowe przeznaczone dla Loggera (np. ectra).

Wydmy
źródło
4
Ten sposób jest zalecany przez docs.python.org/3/howto/ ...
eshizhan
23

Łatwiejszym rozwiązaniem byłoby użycie znakomitego logbookmodułu

import logbook
import sys

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

Lub bardziej kompletne:

>>> 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
źródło
Wygląda to świetnie, ale czy istnieje sposób na uzyskanie milisekund zamiast sekund?
Jeff
@Jeff jasne, dziennik umożliwia definiowanie niestandardowych programów obsługi z niestandardowymi formatami ciągów i ich używanie.
Thomas Orozco,
5
@Jeff Kilka lat później - domyślna precyzja czasu to milisekundy.
Jan Vlcinsky
12

Jak wspominają inne odpowiedzi, formatowanie w nawiasach klamrowych wprowadzone w Pythonie 3.2 jest używane tylko w ciągu formatu, a nie w samych komunikatach dziennika.

Aby włączyć formatowanie w nawiasach klamrowych w aktualnym komunikacie dziennika, możemy małpować kawałek kodu rejestratora.

Poniższe poprawki dotyczą loggingmodułu w celu utworzenia get_loggerfunkcji, która zwróci program rejestrujący, który używa nowego formatu formatowania dla każdego obsługiwanego rekordu dziennika.

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

Stosowanie:

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

Uwagi:

  • W pełni kompatybilny z normalnymi metodami rejestracji (wystarczy wymienić logging.getLoggerz get_logger)
  • Wpłynie tylko na określone loggery utworzone przez get_loggerfunkcję (nie psuje pakietów innych firm).
  • Jeśli rejestrator zostanie ponownie uzyskany podczas normalnego logging.getLogger()połączenia, nadal będzie obowiązywać formatowanie w nowym stylu.
  • kwargs nie są obsługiwane (uniemożliwia konflikt z wbudowaną exc_info, stack_info, stackleveli extra).
  • Wydajność powinna być minimalna (przepisanie pojedynczego wskaźnika funkcji dla każdego komunikatu dziennika).
  • Formatowanie wiadomości jest opóźnione do momentu wyprowadzenia (lub nie ma go wcale, jeśli komunikat dziennika jest filtrowany).
  • Argumenty są przechowywane w logging.LogRecordobiektach jak zwykle (przydatne w niektórych przypadkach w przypadku niestandardowych programów obsługi dziennika).
  • Patrząc na loggingkod źródłowy modułu , wydaje się, że powinien on działać aż do Pythona 2.6, kiedy str.formatzostał wprowadzony (ale testowałem go tylko w 3.5 i nowszych)
pR0Ps
źródło
2
Jedyna odpowiedź, która bierze pod uwagę, że ciąg debugowania powinien być obliczany tylko wtedy, gdy komunikat debugera ma zostać wydrukowany. Dzięki!
Fafaman
2

Wypróbuj logging.setLogRecordFactoryw Pythonie 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
źródło
To działa, ale problem polega na tym, że zepsujesz moduły innych firm, które używają %formatowania, ponieważ fabryka rekordów jest globalna dla modułu rejestrowania.
jtaylor
1

Stworzyłem niestandardowy program formatujący o nazwie ColorFormatter, który rozwiązuje ten problem:

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…

Dzięki temu jest kompatybilny z różnymi bibliotekami. Wadą jest to, że prawdopodobnie nie jest wydajny z powodu potencjalnej próby dwukrotnego sformatowania ciągu.

Gringo Suave
źródło
0

Oto coś naprawdę prostego, które działa:

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

Następnie:

mydebuglog("hello {} {val}", "Python", val="World")
Holenderscy mistrzowie
źródło
0

Podobne rozwiązanie do pR0Ps', owijania getMessagew LogRecordowijając makeRecord(zamiast handlena ich odpowiedź) w przypadkach Logger, które powinny być nowe formatowanie z obsługą:

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

Przetestowałem to w Pythonie 3.5.3.

Dragorn421
źródło