Используя пакет ведения журнала python, как вставить дополнительное форматирование в несколько обработчиков регистратора, которые имеют свои собственные форматеры?
У меня есть один регистратор с несколькими обработчиками, которые имеют свои собственные форматеры. Теперь я хочу добавить функцию отступа, с уровнем отступа, контролируемым во время выполнения. Мне нужны сообщения от всех обработчиков, чтобы получить этот отступ. Я попытался создать его в качестве фильтра, но обнаружил, что, похоже, не могу изменить содержание сообщения. Затем я попробовал его в качестве форматера, но у меня может быть только один обработчик. Как я могу добавить такой отступ без явного изменения форматеров каждого обработчика?
Я должен упомянуть, что один из моих форматеров-это класс, который добавляет цвет к выходным данным. Это не простая строка формата.
Кроме того, я использую конфигурационный файл. В идеале, я хотел бы иметь возможность управлять этим в основном оттуда. Однако мне нужно изменить состояние форматера отступа (например, установить уровень отступа), но я не знаю, как добраться до этого конкретного форматера, так как нет метода logger.getFormatter("by_name")
.
Чтобы уточнить, мне нужно получить доступ к конкретному экземпляру форматера, по существу, чтобы настроить формат на лету. Экземпляр был создан путем ведения журнала.конфигурация из файла. Я не нахожу никаких методов доступа, которые позволили бы мне получить конкретный форматер, названный его именем.
3 ответа:
#!/usr/bin/env python import logging from random import randint log = logging.getLogger("mylog") log.setLevel(logging.DEBUG) class MyFormatter(logging.Formatter): def __init__(self, fmt): logging.Formatter.__init__(self, fmt) def format(self, record): indent = " " * randint(0, 10) # To show that it works msg = logging.Formatter.format(self, record) return "\n".join([indent + x for x in msg.split("\n")]) # Log to file filehandler = logging.FileHandler("indent.txt", "w") filehandler.setLevel(logging.DEBUG) filehandler.setFormatter(MyFormatter("%(levelname)-10s %(message)s")) log.addHandler(filehandler) # Log to stdout too streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.INFO) streamhandler.setFormatter(MyFormatter("%(message)s")) log.addHandler(streamhandler) # Test it log.debug("Can you show me the dog-kennels, please") log.info("They could grip it by the husk") log.warning("That's no ordinary rabbit!") log.error("Nobody expects the spanish inquisition") try: crunchy_frog() except: log.exception("It's a real frog")
Результат:
They could grip it by the husk That's no ordinary rabbit! Nobody expects the spanish inquisition It's a real frog Traceback (most recent call last): File "./logtest2.py", line 36, in crunchy_frog() NameError: name 'crunchy_frog' is not definedЯ не уверен, что понял ваш второй вопрос.
Хорошо, вот один из способов, который дает мне почти то, что мне нужно. Подкласс LogRecord перезаписать getMessage для вставки отступа и субкласс logger сделать запись с ним:
import logging import logging.config ################################################################################ class IndentingLogger(logging.Logger): """A Logger subclass to add indent on top of any logger output """ ############################################################################ def __init__(self, name = 'root', logging_level = logging.NOTSET): "Constructor to keep indent persistent" logging.Logger.__init__(self, name, logging_level) self.indenter = IndentedRecord("", logging.NOTSET, "", 0, None, None, None, None, None) ############################################################################ def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): return self.indenter.set_record(name, level, fn, lno, msg, args, exc_info, func, extra) ################################################################################ class IndentedRecord(logging.LogRecord): """A LogRecord subclass to add indent on top of any logger output """ ######## Class data ######### DEFAULT_INDENT_STR = ' ' ############################################################################ def __init__(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): "Constructor" logging.LogRecord.__init__(self, name, level, fn, lno, msg, args, exc_info, func) self._indent_level = 0 self._indent_str_base = IndentedRecord.DEFAULT_INDENT_STR self._indent_str = "" # cache it ############################################################################ def set_record(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): "Constructs the base record" logging.LogRecord.__init__(self, name, level, fn, lno, msg, args, exc_info, func) return self ################################################################################ def getMessage(self): "Adds indent on top of the normal getMessage result" # Call base class to get the formatted message message = logging.LogRecord.getMessage(self) # Now insert the indent return self._indent_str + message ################################################################################ def indent(self, step = 1): "Change the current indent level by the step (use negative to decrease)" self._indent_level += step if self._indent_level < 0: self._indent_level = 0 self._indent_str = self._indent_str_base * self._indent_level ################################################################################ def set_indent_str(self, chars): "Change the current indent string" if not isinstance(chars, str): raise ValueError("Argument must be a string. Got %s" % chars) self._indent_str_base = chars logging.config.fileConfig("reporter.conf") logging.setLoggerClass(IndentingLogger) logger = logging.getLogger('root') # will be wrong logger, if without argument logger.debug("debug message") logger.info("info message") logger.indenter.indent(+1) logger.warn("Indented? warn message") logger.indenter.set_indent_str("***") logger.error("Indented? error message: %s", "Oops, I did it again!") logger.indenter.indent(+1) logger.error("Indented? error message: %s", "Oops, I did it again!") logger.indenter.indent(-1) logger.critical("No indent; critical message")
Результат (окрашенный в реальности):
Debug: debug message Info: info message Warning: Indented? warn message Error: Indented? error message: Oops, I did it again! Error: ******Indented? error message: Oops, I did it again! Internal Error: ***No indent; critical message
Каким-то образом строка уровня журнала все еще пробирается вперед, так что это не совсем то, что я хочу. Кроме того, это неудобно - слишком много для такой простой функции : (
Есть идеи получше?
Вот еще один, избитый, но простой. Мои сообщения для всех обработчиков всегда начинаются со строки уровня сообщения. Просто модифицируйте эти чертовы строки при каждом изменении отступа:
# (make a LEVELS dict out of all the logging levels first) def indent(self, step = 1): "Change the current indent level by the step (use negative to decrease)" self._indent_level += step if self._indent_level < 0: self._indent_level = 0 self._indent_str = self._indent_str_base * self._indent_level for lvl in LEVELS: level_name = self._indent_str + LEVELS[lvl] logging.addLevelName(lvl, level_name)
(смотрите мой другой ответ для материала, который окружает функцию отступа)
Теперь индентор может быть независимым классом, не вмешиваясь в детали процесса протоколирования. До тех пор, пока сообщение содержит строку уровня, отступ будет там, даже если некоторые вещи приходят до него. Не идеально в генерал, но может работать на меня.
У кого-нибудь есть еще идеи, которые работают для любого формата msg?