Заставить Python logger выводить все сообщения на стандартный вывод в дополнение к файлу журнала

450

Есть ли способ заставить журналирование Python с помощью loggingмодуля автоматически выводить вещи на стандартный вывод в дополнение к файлу журнала, куда они должны идти? Например, я хотел бы все вызовы logger.warning, logger.critical, logger.errorчтобы перейти к их предназначенных для этого местах , но в дополнение всегда быть скопированы stdout. Это сделано для того, чтобы избежать дублирования сообщений, таких как:

mylogger.critical("something failed")
print "something failed"
Бен
источник
1
Пожалуйста, проверьте этот ответ stackoverflow.com/questions/9321741/…
SeF

Ответы:

636

Весь вывод журнала обрабатывается обработчиками; просто добавьте logging.StreamHandler()в корневой логгер.

Вот пример настройки потокового обработчика (с использованием stdoutвместо значения по умолчанию stderr) и добавления его в корневой логгер:

import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
Мартейн Питерс
источник
4
Это нормально, но если он уже перенаправлен в файл, как я могу дополнительно распечатать его stdout?
54
@ user248237: добавив новый обработчик, как показано на рисунке. Новые обработчики не заменяют существующие обработчики, они также обрабатывают записи журнала.
Мартин Питерс
@MartijnPieters есть ли способ добавить строку в каждую распечатанную инструкцию?
Прахар Мохан Шривастава
7
@PrakharMohanSrivastava Я думаю, вы можете просто добавить его к переданной строке logging.Formatter.
А.Ван
3
@ himanshu219: вариант использования состоит в том, что как только вы начинаете добавлять несколько обработчиков, вы обычно хотите различать. ОТЛАДКА на консоль, ПРЕДУПРЕЖДЕНИЕ и до файла и т. Д.
Мартин Питерс
506

Самый простой способ войти в stdout:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
Эяль
источник
58
Хм, но это не записано в файл, верно? Вопрос заключался в том, как сделать запись в файл и на консоль.
Weidenrinde
В Python 3, по крайней мере, похоже, что пропуски stream=sys.stdoutвсе еще работают для входа в консоль для меня.
Тейлор Эдмистон
3
@TaylorEdmiston Да, но это поток stderr AFAIK. Попробуйте перенаправить вывод из оболочки.
Сорин
1
ХОРОШО. Это не отвечает обоим: вход в файл и на консоль, но было приятно найти то, что мне нужно, в 3 строки или меньше.
Steve3p0
67

Это возможно с использованием нескольких обработчиков.

import logging
import auxiliary_module

# create logger with 'spam_application'
log = logging.getLogger('spam_application')
log.setLevel(logging.DEBUG)

# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
log.addHandler(fh)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
ch.setFormatter(formatter)
log.addHandler(ch)

log.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
log.info('created an instance of auxiliary_module.Auxiliary')

log.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
log.info('finished auxiliary_module.Auxiliary.do_something')

log.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
log.info('done with auxiliary_module.some_function()')

# remember to close the handlers
for handler in log.handlers:
    handler.close()
    log.removeFilter(handler)

Пожалуйста, смотрите: https://docs.python.org/2/howto/logging-cookbook.html

Алок Сингх Махор
источник
4
Прекрасный ответ, хотя и немного грязный. Мне нравится, как вы показываете, как использовать разные уровни и форматы для потоков и файлов. +1, но +2 по духу.
Несчастный кот
Для меня это не сработало без sys.stdoutпараметра inch = logging.StreamHandler()
veuncent
64

Вы можете создать два обработчика для файла и стандартного вывода, а затем создать один регистратор с handlersаргументом для basicConfig. Это может быть полезно, если у вас одинаковый log_level и формат вывода для обоих обработчиков:

import logging
import sys

file_handler = logging.FileHandler(filename='tmp.log')
stdout_handler = logging.StreamHandler(sys.stdout)
handlers = [file_handler, stdout_handler]

logging.basicConfig(
    level=logging.DEBUG, 
    format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
    handlers=handlers
)

logger = logging.getLogger('LOGGER_NAME')
Антон Протопопов
источник
32

Самый простой способ войти в файл и в stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
Weidenrinde
источник
Это не показывает метки INFO, DEBUG и ERROR до сообщения регистрации в консоли. Он показывает эти метки в файле. Любые идеи, чтобы также показать метки в консоли?
JahMyst
1
Спасибо, @JahMyst, я добавил Форматтер. К сожалению, это уже не так коротко, но все же самый простой способ. :-)
Weidenrinde
12

Вот решение, основанное на мощном, но плохо документированном logging.config.dictConfigметоде . Вместо того, чтобы отправлять каждое сообщение журнала stdout, он отправляет сообщения с уровнем журнала ERRORи выше stderrи всем остальным stdout. Это может быть полезно, если другие части системы слушают stderrили stdout.

import logging
import logging.config
import sys

class _ExcludeErrorsFilter(logging.Filter):
    def filter(self, record):
        """Filters out log messages with log level ERROR (numeric value: 40) or higher."""
        return record.levelno < 40


config = {
    'version': 1,
    'filters': {
        'exclude_errors': {
            '()': _ExcludeErrorsFilter
        }
    },
    'formatters': {
        # Modify log message format here or replace with your custom formatter class
        'my_formatter': {
            'format': '(%(process)d) %(asctime)s %(name)s (line %(lineno)s) | %(levelname)s %(message)s'
        }
    },
    'handlers': {
        'console_stderr': {
            # Sends log messages with log level ERROR or higher to stderr
            'class': 'logging.StreamHandler',
            'level': 'ERROR',
            'formatter': 'my_formatter',
            'stream': sys.stderr
        },
        'console_stdout': {
            # Sends log messages with log level lower than ERROR to stdout
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filters': ['exclude_errors'],
            'stream': sys.stdout
        },
        'file': {
            # Sends all log messages to a file
            'class': 'logging.FileHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filename': 'my.log',
            'encoding': 'utf8'
        }
    },
    'root': {
        # In general, this should be kept at 'NOTSET'.
        # Otherwise it would interfere with the log levels set for each handler.
        'level': 'NOTSET',
        'handlers': ['console_stderr', 'console_stdout', 'file']
    },
}

logging.config.dictConfig(config)
Элиас Штреле
источник
пришлось переименовать регистратор в пустую строку, чтобы фактически получить корневой регистратор. В противном случае очень полезно, спасибо!
Newtopian
8

Так как никто не разделил аккуратные два лайнера, я поделюсь своим собственным:

logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
logging.getLogger().addHandler(logging.StreamHandler())
Lexander
источник
2

Вот очень простой пример:

import logging
l = logging.getLogger("test")

# Add a file logger
f = logging.FileHandler("test.log")
l.addHandler(f)

# Add a stream logger
s = logging.StreamHandler()
l.addHandler(s)

# Send a test message to both -- critical will always log
l.critical("test msg")

Вывод покажет "test msg" на стандартный вывод, а также в файл.

Кики Джуэлл
источник