Оценка строки сообщения ленивого регистратора
Я использую стандартный модуль ведения журнала python в моем приложении python:
import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("log") while True: logger.debug('Stupid log message " + ' '.join([str(i) for i in range(20)]) ) # Do something
проблема в том, что хотя уровень отладки не включен, это глупое сообщение журнала оценивается на каждой итерации цикла, что плохо влияет на производительность.
есть ли решение для этого?
В C++ у нас есть log4cxx
пакет, который предоставляет макросы следующим образом:LOG4CXX_DEBUG(logger, messasage)
Это эффективно оценивает в
if (log4cxx::debugEnabled(logger)) { log4cxx.log(logger,log4cxx::LOG4CXX_DEBUG, message) }
но так как в Python (AFAIK) нет макросов, если есть эффективный способ ведения журнала?
5 ответов:
модуль регистрации уже имеет частичную поддержку того, что вы хотите сделать. Сделайте это:
log.debug("Some message: a=%s b=%s", a, b)
... вместо этого:
log.debug("Some message: a=%s b=%s" % (a, b))
модуль регистрации достаточно умен, чтобы не создавать полное сообщение журнала, если сообщение фактически не регистрируется где-то.
чтобы применить эту функцию к вашему конкретному запросу, вы можете создать класс lazyjoin.
class lazyjoin: def __init__(self, s, items): self.s = s self.items = items def __str__(self): return self.s.join(self.items)
используйте его следующим образом (обратите внимание на использование выражения генератора, добавив к лень):
logger.info('Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20))))
вот демо, которое показывает, что это работает.
>>> import logging >>> logging.basicConfig(level=logging.INFO) >>> logger = logging.getLogger("log") >>> class DoNotStr: ... def __str__(self): ... raise AssertionError("the code should not have called this") ... >>> logger.info('Message %s', DoNotStr()) Traceback (most recent call last): ... AssertionError: the code should not have called this >>> logger.debug('Message %s', DoNotStr()) >>>
в демо-версии, logger.info () вызов попал в ошибку утверждения, в то время как регистратор.debug () не зашел так далеко.
конечно, не так эффективно, как макрос:
if logger.isEnabledFor(logging.DEBUG): logger.debug( 'Stupid log message ' + ' '.join([str(i) for i in range(20)]) )
но простой, оценивает в ленивой моде и в 4 раза быстрее принятого ответа:
class lazyjoin: def __init__(self, s, items): self.s = s self.items = items def __str__(self): return self.s.join(self.items) logger.debug( 'Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20))) )
посмотреть benchmark-src для моей установки.
import logging import time logging.basicConfig(level=logging.INFO) logger = logging.getLogger("log") class Lazy(object): def __init__(self,func): self.func=func def __str__(self): return self.func() logger.debug(Lazy(lambda: time.sleep(20))) logger.info(Lazy(lambda: "Stupid log message " + ' '.join([str(i) for i in range(20)]))) # INFO:log:Stupid log message 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Если вы запустите скрипт, вы заметите первый
logger.debug
выполнение команды не занимает 20 секунд. Это показывает, что аргумент не вычисляется, когда уровень ведения журнала ниже установленного уровня.
как указывает Шейн, используя
log.debug("Some message: a=%s b=%s", a, b)
... вместо этого:
log.debug("Some message: a=%s b=%s" % (a, b))
экономит некоторое время, выполняя только форматирование строки, если сообщение действительно зарегистрировано.
это не полностью решает проблему, хотя, как вы, возможно, придется предварительно обработать значения для форматирования в строку, например:
log.debug("Some message: a=%s b=%s", foo.get_a(), foo.get_b())
в этом случае
obj.get_a()
иobj.get_b()
будет вычисляться даже если нет регистрации происходит.решением этого было бы использование лямбда-функций, но для этого требуется дополнительное оборудование:
class lazy_log_debug(object): def __init__(self, func): self.func = func logging.debug("%s", self) def __str__(self): return self.func()
... затем вы можете войти в систему со следующим:
lazy_log_debug(lambda: "Some message: a=%s b=%s" % (foo.get_a(), foo.get_b()))
в этом случае лямбда-функция будет только, если
log.debug
решает выполнить форматирование, поэтому вызов__str__
метод.имейте в виду: накладные расходы на это решение могут очень хорошо превышать выгоду :-) но, по крайней мере, теоретически, это делает это можно сделать совершенно ленивый журнал.
Я представляю,
Lazyfy
:class Lazyfy(object): __slots__ = 'action', 'value' def __init__(self, action, *value): self.action = action self.value = value def __str__(self): return self.action(*self.value)
использование:
from pprint import pformat log.debug("big_result: %s", Lazyfy(pformat, big_result)) log.debug( "x y z: %s", Lazyfy( lambda x, y, z: ' ,'.join( [x, y, z] ), '1', '2', '3' ) )
исходный пример:
logger.info('Stupid log message %s', Lazyfy(lambda: ' '.join((str(i) for i in range(20)))))
как вы видите, это касается и другой ответ, который использует лямбда-функции, но использует больше памяти
value
атрибут и расширение. Тем не менее, это экономит больше памяти с: использование __слот__?наконец, безусловно, наиболее эффективным решением по-прежнему является следующее, Как предложил другой ответ:
if logger.isEnabledFor(logging.DEBUG): logger.debug('Stupid log message ' + ' '.join([str(i) for i in range(20)]))