Многострочный журнал в Python

Я использую Python 3.3.5 и модуль регистрации для записи информации в локальный файл (из разных потоков). Есть случаи, когда я хотел бы выводить некоторую дополнительную информацию, не зная точно, что это за информация (например, это может быть одна строка текста или dict).

Что бы я хотел сделать, это добавить эту дополнительную информацию в мой файл журнала после записи записи журнала. Кроме того, дополнительная информация необходима только тогда, когда уровень журнала является ошибкой (или выше).

В идеале это выглядит примерно так:

2014-04-08 12:24:01 - INFO     - CPU load not exceeded
2014-04-08 12:24:26 - INFO     - Service is running
2014-04-08 12:24:34 - ERROR    - Could not find any active server processes
Additional information, might be several lines.
Dict structured information would be written as follows:
key1=value1
key2=value2
2014-04-08 12:25:16 - INFO     - Database is responding

За исключением написания настраиваемого формата журнала, я не мог найти многого, что соответствовало моим требованиям. Я читал об фильтрах и контекстах, но опять же это не похоже на хорошее совпадение.

В качестве альтернативы, я мог бы просто записать файл с использованием стандартного ввода-вывода, но большинство функций уже существует в модуле ведения журнала, и, кроме того, оно поточно-безопасно.

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

Ответ 1

Я просто добавляю символы \n к выходному тексту.

Ответ 2

Помня о том, что многие люди считают сообщение о многострочном протоколировании плохой практикой, вы можете играть с атрибутом extra и использовать собственный форматтер для добавления материала в сообщение, которое будет показано (посмотрите к использованию "extra" в протоколе регистрации документации).

import logging

class CustomFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, 'dct') and len(record.dct) > 0:
            for k, v in record.dct.iteritems():
                record.msg = record.msg + '\n\t' + k + ': ' + v
        return super(CustomFilter, self).filter(record)

if __name__ == "__main__":
    logging.getLogger().setLevel(logging.DEBUG)
    extra_logger = logging.getLogger('extra_logger')
    extra_logger.setLevel(logging.INFO)
    extra_logger.addFilter(CustomFilter())
    logging.debug("Nothing special here... Keep walking")
    extra_logger.info("This shows extra",
                      extra={'dct': {"foo": "bar", "baz": "loren"}})
    extra_logger.debug("You shouldn't be seeing this in the output")
    extra_logger.setLevel(logging.DEBUG)
    extra_logger.debug("Now you should be seeing it!")

Этот код выводит:

DEBUG:root:Nothing special here... Keep walking
INFO:extra_logger:This shows extra
        foo: bar
        baz: loren
DEBUG:extra_logger:Now you should be seeing it!

Я по-прежнему рекомендую вызывать функцию super filter в вашем настраиваемом фильтре, главным образом потому, что функция, которая решает, показывать ли сообщение или нет (например, если ваш уровень регистратора установлен на logging.INFO, а вы log something using extra_logger.debug, это сообщение не должно быть видно, как показано в примере выше)

Ответ 3

Кажется, что я сделал небольшую опечатку при определении моей строки LogFormatter: случайно избегая символа новой строки, я ошибочно предположил, что запись многострочного вывода в файл журнала невозможна.

Приветствуем @Barafu за то, что указали это (вот почему я назначил ему правильный ответ).

Здесь пример кода:

import logging
lf = logging.Formatter('%(levelname)-8s - %(message)s\n%(detail)s')
lh = logging.FileHandler(filename=r'c:\temp\test.log')
lh.setFormatter(lf)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(lh)
log.debug('test', extra={'detail': 'This is a multi-line\ncomment to test the formatter'})

Результат будет выглядеть так:

DEBUG    - test
This is a multi-line
comment to test the formatter

Протест:

Если в журнале нет подробной информации, и вы передаете пустую строку, журнал будет по-прежнему выводить новую строку. Таким образом, оставшийся вопрос: как мы можем сделать это условным?

Один из подходов заключается в обновлении форматера протоколирования до фактической регистрации информации, как описано здесь.

Ответ 4

я использую простой разветкитель линии в своих небольших приложениях:

for line in logmessage.splitlines():
            writemessage = logtime + " - " + line + "\n"
            logging.info(str(writemessage))

Обратите внимание, что это не потокобезопасно и, вероятно, должно использоваться только в приложениях регистрации тома журнала.

Однако вы можете вывести в журнал почти все, что сохранит ваше форматирование. Я использовал его, например, для вывода ответов JSON API, отформатированных с использованием: json.dumps(parsed, indent=4, sort_keys=True)