Используя пакет ведения журнала python, как вставить дополнительное форматирование в несколько обработчиков регистратора, которые имеют свои собственные средства форматирования?

У меня есть один регистратор с несколькими обработчиками, у которых есть свои форматировщики. Теперь я хочу добавить функцию отступа, при этом уровень отступа будет контролироваться во время выполнения. Я хочу, чтобы сообщения от всех обработчиков получали этот отступ. Я пытался создать его как фильтр, но обнаружил, что не могу изменить содержимое сообщения. Затем я попробовал его как средство форматирования, но у меня может быть только один обработчик. Как я могу добавить такой отступ без явного изменения средств форматирования каждого обработчика?
Я должен упомянуть, что один из форматтеров, которые у меня есть, — это класс, добавляющий цвет к выходным данным. Это не простая строка формата.


Кроме того, я использую файл конфигурации. В идеале я хотел бы иметь возможность вести это в основном оттуда. Однако мне нужно изменить состояние средства форматирования отступов (например, установить уровень отступа), но я не знаю, как добраться до этого конкретного средства форматирования, поскольку нет метода logger.getFormatter("by_name").
Чтобы уточнить, мне нужно получить доступ к конкретному экземпляру средства форматирования, по сути, для настройки формата на лету. Экземпляр был создан с помощью logging.config из файла. Я не нахожу никаких методов доступа, которые позволили бы мне получить конкретное средство форматирования по его имени.


person Evgen    schedule 20.01.2011    source источник


Ответы (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

Я не уверен, что понял ваш второй вопрос.

person Lauritz V. Thaulow    schedule 20.01.2011
comment
Это не сработает для меня: один из форматировщиков, которые у меня есть, — это класс, который добавляет цвет к выводу. Это не простая строка формата. Кроме того, в файле конфигурации я не могу указать строку формата в разделе обработчика, если я также укажу средство форматирования. Строка формата игнорируется. Похоже, он хочет всегда видеть его с разделом форматирования. Я также уточнил вторую половину вопроса. - person Evgen; 20.01.2011

Вот еще один, хакерский, но простой. Мои сообщения для всех обработчиков всегда начинаются со строки уровня сообщения. Просто изменяйте эти проклятые строки при каждом изменении отступа:

# (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?

person Evgen    schedule 20.01.2011

Хорошо, вот один из способов, который дает мне ПОЧТИ то, что мне нужно. Подкласс LogRecord для перезаписи getMessage для вставки отступа и подкласс logger для makeRecord с ним:

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

Каким-то образом строка уровня журнала все еще пробирается вперед, так что это не совсем то, что я хочу. Кроме того, это неудобно - слишком много для такой простой функции :(
Идеи получше?

person Evgen    schedule 20.01.2011