Запись переменных данных с помощью новой строки формата
Я использую средство ведения журнала для python 2.7.3. Документация для этой версии Python говорит:
пакет протоколирования предварительно задает новые параметры форматирования, такие как str.format() и string.Template. Эти новые параметры форматирования поддерживаются...
Мне нравится "новый" формат с фигурными фигурными скобками. Поэтому я пытаюсь сделать что-то вроде:
log = logging.getLogger("some.logger")
log.debug("format this message {0}", 1)
И получите ошибку:
TypeError: не все аргументы, преобразованные во время форматирования строки
Что мне здесь не хватает?
P.S. Я не хочу использовать
log.debug("format this message {0}".format(1))
потому что в этом случае сообщение всегда отформатируется независимо от уровня регистратора.
Ответы
Ответ 1
EDIT: посмотрите на StyleAdapter
подход в ответ @Dunes' в отличие от этого ответа; он позволяет использовать альтернативные стили форматирования без шаблона при вызове методов регистрации (debug(), info(), error() и т.д.).
Из документов - Использование альтернативных стилей форматирования:
Журнальные вызовы (logger.debug(), logger.info() и т.д.) Принимают только позиционные параметры для самого фактического сообщения регистрации, причем параметры ключевого слова используются только для определения параметров обработки фактического вызова ведения журнала (например, параметр ключевого слова exc_info чтобы указать, что информация трассировки должна быть зарегистрирована, или дополнительный параметр ключевого слова, чтобы указать дополнительную контекстуальную информацию, которая будет добавлена в журнал). Таким образом, вы не можете напрямую вести вызовы журналов с использованием синтаксиса str.format() или string.Template, потому что внутри пакета протоколирования используется% -formatting для объединения строки формата и аргументов переменной. Это не изменило бы это, сохранив обратную совместимость, поскольку все вызовы журналов, которые есть в существующем коде, будут использовать строки% -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 не регистрируются, форматирование вообще не выполняется.
Ответ 2
Вот еще один вариант, который не имеет проблем с ключевыми словами, упомянутых в ответе Дюны. Он может обрабатывать только аргументы positional ({0}
), а не ключевые слова ({foo}
). Он также не требует двух вызовов для форматирования (с использованием подчеркивания). Он имеет ick-фактор подклассификации 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
Ответ 3
Более простым решением было бы использовать отличный модуль 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
Ответ 4
Это было мое решение проблемы, когда я обнаружил, что для ведения журнала используется только форматирование стиля 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} substiution", type="brace")
Стоит отметить, что эта реализация имеет проблемы, если ключевые слова, используемые для подстановки фигурных скобок, включают в себя level
, msg
, args
, exc_info
, extra
или stack_info
. Это имена аргументов, используемые методом log
Logger
. Если вам нужно одно из этих имен, измените process
, чтобы исключить эти имена или просто удалить log_kwargs
из вызова _log
. Следующее примечание: эта реализация также молча игнорирует слова с ошибками, используемые для Logger (например, ectra
).
Ответ 5
Как упоминается в других ответах, форматирование стиля представленное в Python 3.2, используется только в строке формата, а не в фактических сообщениях журнала.
Как и у Python 3.5, нет хорошего способа использовать форматирование в стиле скобок для записи сообщений.
Однако, как и в большинстве случаев на Python, есть не очень хороший способ.
Следующая обезьяна исправляет модуль 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
it 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 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>
Примечания:
- Будет влиять только на определенные журналы, созданные функцией
get_logger
.
- Если регистратор снова получает доступ из обычного вызова
logging.getLogger()
, форматирование нового стиля будет по-прежнему применяться
- kwargs не поддерживается
- Достижение производительности должно быть минимальным (переписывание одного указателя функции для каждого сообщения журнала)
- Форматирование сообщения задерживается до его выхода
- Не останавливает хранение аргументов на объектах
logging.LogRecord
(полезно в некоторых случаях)
- От взгляда на исходный код
logging
кажется, что он должен полностью вернуться к Python 2.6, когда str.format
был введен (но был протестирован только на Python 3.5).
Ответ 6
Здесь что-то реальное простое, что работает:
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")
Ответ 7
Попробуйте 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)
Ответ 8
Я создал пользовательский Formatter, называемый 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…
Это совместимо с различными библиотеками. Недостатком является то, что он, вероятно, не работает из-за потенциальной попытки форматирования строки дважды.