Регистрация переменных данных с новой строкой формата

85

Я использую средство ведения журнала для Python 2.7.3. В документации для этой версии Python говорится :

пакет регистрации предшествует новым параметрам форматирования, таким как str.format () и string.Template. Эти новые параметры форматирования поддерживаются ...

Мне нравится «новый» формат с фигурными скобками. Итак, я пытаюсь сделать что-то вроде:

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

И получите ошибку:

TypeError: не все аргументы преобразуются во время форматирования строки

Что мне здесь не хватает?

PS не хочу использовать

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

потому что в этом случае сообщение всегда форматируется независимо от уровня регистратора.

MajesticRa
источник
1
Вы можете сделать это: log.debug("format this message%d" % 1)
ronak
1
вам необходимо настроить Formatterиспользование '{' в качестве стиля
mata
2
@ronak Спасибо за совет, но нет. Пожалуйста, смотрите раздел "ps" почему. Кстати, log.debug ("форматировать это сообщение% d", 1) - отлично работает.
MajesticRa
@mata Как это настроить? Есть ли прямая документация об этом?
MajesticRa
@mata Я нашел это. Пожалуйста, дайте ответ, чтобы я мог установить его как «правильный ответ. Еще раз спасибо.
MajesticRa

Ответы:

38

РЕДАКТИРОВАТЬ: взгляните на StyleAdapterподход в ответе @Dunes в отличие от этого ответа; он позволяет использовать альтернативные стили форматирования без шаблона при вызове методов регистратора (debug (), info (), error () и т. д.).


Из документации - Использование альтернативных стилей форматирования :

Вызовы журналирования (logger.debug (), logger.info () и т. Д.) Принимают только позиционные параметры для самого фактического сообщения журнала, с параметрами ключевого слова, используемыми только для определения вариантов того, как обрабатывать фактический вызов журнала (например, параметр ключевого слова exc_info чтобы указать, что информация трассировки должна регистрироваться, или параметр дополнительного ключевого слова, чтобы указать дополнительную контекстную информацию, которая должна быть добавлена ​​в журнал). Таким образом, вы не можете напрямую выполнять вызовы журналирования, используя синтаксис str.format () или string.Template, потому что внутри пакета журналирования используется% -форматирование для объединения строки формата и переменных аргументов. Это не изменится при сохранении обратной совместимости, поскольку все вызовы журналирования, которые присутствуют в существующем коде, будут использовать строки% -format.

И:

Однако есть способ, которым вы можете использовать {} - и $ - форматирование для создания ваших индивидуальных сообщений журнала. Напомним, что для сообщения вы можете использовать произвольный объект в качестве строки формата сообщения, и что пакет регистрации будет вызывать str () для этого объекта, чтобы получить фактическую строку формата.

Скопируйте и вставьте это в whereverмодуль:

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)

Потом:

from wherever import BraceMessage as __

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

Примечание: фактическое форматирование откладывается до тех пор, пока оно не станет необходимым, например, если сообщения DEBUG не регистрируются, форматирование не выполняется вообще.

jfs
источник
4
Начиная с Python 3.6, вы можете использовать f-строки следующим образом:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose 06
12
@ P1h3r1e3d13 в отличие от кода регистрации в ответе f '' - строки выполняют форматирование немедленно.
jfs
1
Правильно. Они работают здесь, потому что форматируют и возвращают обычную строку перед вызовом метода журнала. Это может иметь отношение или не иметь отношения к кому-то, поэтому я думаю, что стоит упомянуть как вариант.
Jacktose
6
@Jacktose Я думаю, что пользователи не должны регистрироваться с использованием f-строк, это побеждает службы агрегирования журналов (например, часовую). Есть веская причина, по которой ведение журнала stdlib откладывает создание шаблона строки.
wim
30

Вот еще один вариант, в котором нет проблем с ключевыми словами, упомянутых в ответе Дюны. Он может обрабатывать только {0}аргументы positional ( ), но не аргументы keyword ( {foo}). Также не требуется двух вызовов форматирования (с использованием подчеркивания). У него есть фактор подкласса 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

Вы используете это так:

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

Конечно, вы можете удалить отметку, отмеченную значком, # optionalчтобы заставить все сообщения, проходящие через адаптер, использовать форматирование нового стиля.


Примечание для тех, кто читает этот ответ спустя годы : начиная с Python 3.2 , вы можете использовать параметр стиля с Formatterобъектами:

Ведение журнала (начиная с версии 3.2) обеспечивает улучшенную поддержку этих двух дополнительных стилей форматирования. Класс Formatter был расширен и теперь принимает дополнительный необязательный параметр ключевого слова с именем style. По умолчанию это '%', но другие возможные значения - '{'и '$', которые соответствуют двум другим стилям форматирования. Обратная совместимость поддерживается по умолчанию (как и следовало ожидать), но, явно указав параметр стиля, вы получаете возможность указать строки формата, которые работают с str.format()или string.Template.

Документы предоставляют пример logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Обратите внимание, что в этом случае вы по-прежнему не можете вызвать loggerс новым форматом. Т.е. по-прежнему не сработает следующее:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either
Фелипе
источник
6
Ваше утверждение о Python 3 неверно. Параметр style применяется только к строке формата Formatter, но не к отдельным сообщениям журнала. На странице, на которую вы ссылаетесь, прямо говорится: «Это невозможно изменить, сохранив обратную совместимость».
mhsmith
1
Спасибо, что держал меня честным. Первая часть сейчас менее полезна, но я перефразировал ее в терминах Formatter, что сейчас правильно (я думаю). Все StyleAdapter еще работает,
Фелипе
@falstro - спасибо, что указали на это. Обновленная версия теперь должна работать. Поскольку BraceStringэто строковый подкласс, можно безопасно вернуться из__str__
Фелипе
1
единственный ответ, в котором упоминается style = "{", +1
Том С.
24

Более простым решением было бы использовать отличный logbookмодуль

import logbook
import sys

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

Или более полный:

>>> 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
Томас Ороско
источник
Это выглядит великолепно, но есть ли способ использовать миллисекунды, а не секунды?
Джефф
@Jeff, конечно, журнал позволяет вам определять пользовательские обработчики и использовать пользовательские строковые форматы.
Thomas Orozco
5
@Jeff Пару лет спустя - точность времени по умолчанию составляет миллисекунды.
Ян Влчинский,
24

Это было моим решением проблемы, когда я обнаружил, что при ведении журнала используется только форматирование в стиле printf. Это позволяет вести журнал вызовов неизменным - без специального синтаксиса, такого как log.info(__("val is {}", "x")). Изменение, необходимое для кода, - заключить регистратор в файл 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}

Использование:

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

Стоит отметить , что эта реализация имеет проблемы , если ключевые слова , используемые для замены распорной включают level, msg, args, exc_info, extraили stack_info. Это имена аргументов, используемые logметодом Logger. Если вам нужно одно из этих имен, измените, processчтобы исключить эти имена или просто удалить log_kwargsиз _logвызова. Кроме того, эта реализация также молча игнорирует ключевые слова с ошибками, предназначенные для Регистратора (например ectra).

Дюны
источник
4
Этот способ рекомендуется python doc, docs.python.org/3/howto/…
eshizhan
12

Как упоминается в других ответах, форматирование в стиле скобок, представленное в Python 3.2, используется только в строке формата, а не в фактических сообщениях журнала.

Чтобы включить форматирование в виде фигурных скобок для фактического сообщения журнала, мы можем исправить немного кода регистратора.

Следующие исправления loggingмодуля позволяют создать get_loggerфункцию, которая будет возвращать средство ведения журнала, использующее форматирование нового стиля для каждой обрабатываемой записи журнала.

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

Применение:

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

Ноты:

  • Полностью совместим с обычными методами ведения журнала (просто замените logging.getLoggerна get_logger)
  • Влияет только на определенные регистраторы, созданные get_loggerфункцией (не нарушает сторонние пакеты).
  • Если к регистратору снова обращаются из обычного logging.getLogger()вызова, форматирование в новом стиле все равно будет применяться.
  • kwargs не поддерживается ( не дает возможность конфликт с встроенным exc_info, stack_info, stacklevelи extra).
  • Падение производительности должно быть минимальным (переписывание одного указателя функции для каждого сообщения журнала).
  • Форматирование сообщения откладывается до тех пор, пока оно не будет выведено (или вообще не будет, если сообщение журнала отфильтровано).
  • Аргументы хранятся в logging.LogRecordобъектах как обычно (полезно в некоторых случаях с пользовательскими обработчиками журналов).
  • Глядя на loggingисходный код модуля, кажется, что он должен работать вплоть до Python 2.6, когда str.formatбыл представлен (но я тестировал его только в версии 3.5 и выше).
pR0Ps
источник
2
Единственный ответ, который считает, что строка отладки должна вычисляться только в том случае, если сообщение отладчика должно быть напечатано. Благодаря!
Fafaman
2

Попробуйте logging.setLogRecordFactoryв 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
источник
Это действительно работает, но проблема в том, что вы нарушаете сторонние модули, которые используют %форматирование, поскольку фабрика записей является глобальной для модуля ведения журнала.
jtaylor
1

Я создал собственный форматировщик под названием ColorFormatter, который решает проблему следующим образом:

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…

Это сохраняет его совместимость с различными библиотеками. Недостатком является то, что он, вероятно, неэффективен из-за возможной попытки форматирования строки дважды.

Гринго Вежливый
источник
0

Аналогичное решение для pR0Ps ', заключающееся getMessageв LogRecordупаковку makeRecord(вместо handleих ответа) в тех случаях, Loggerкогда должно быть включено новое форматирование:

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

Я тестировал это с помощью Python 3.5.3.

Драгорн421
источник
Это определяет, куда идет нагрузка от фактической интерполяции строки. Вы загружаете его заранее во время создания записи, гарантируя, что статическая строка - это то, что ускользает от серверной части, или вы выполняете форматирование только в том случае, если сообщение в конечном итоге отображается. Простой случай: сообщение фактически ниже допустимого уровня для отображения. Вдобавок: это не лучший способ «исправить» ситуацию. На самом деле создайте подкласс Logger и используйте его, чувак.
амцгрегор
-1

Вот что-то очень простое, что работает:

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

Потом:

mydebuglog("hello {} {val}", "Python", val="World")
Голландские мастера
источник