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

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

mylogger.critical("something failed")
print "something failed"

person Community    schedule 27.12.2012    source источник
comment
Проверьте этот ответ stackoverflow.com/questions/9321741/   -  person SeF    schedule 15.02.2018


Ответы (10)


Весь вывод журнала обрабатывается обработчиками; просто добавьте 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)
person Martijn Pieters    schedule 27.12.2012
comment
Это нормально, но если он уже перенаправлен в файл, как я могу дополнительно распечатать его в stdout? - person ; 27.12.2012
comment
@ user248237: добавив обработчик new, как показано. Новые обработчики не заменяют существующие обработчики, они также должны обрабатывать записи журнала. - person Martijn Pieters; 27.12.2012
comment
@MartijnPieters есть ли способ добавить строку к каждому распечатанному оператору журнала? - person praxmon; 03.12.2015
comment
@PrakharMohanSrivastava Думаю, вы можете просто добавить его в строку, переданную в logging.Formatter. - person A.Wan; 13.01.2016
comment
@MartijnPieters Почему setLevel выполняется несколько раз как в StreamHandler, так и на корневом уровне? - person himanshu219; 25.10.2018
comment
@ himanshu219: логгер имеет уровень, а обработчик - уровень. Регистратор будет обрабатывать сообщения этого уровня и выше, а обработчик будет обрабатывать сообщения этого уровня и выше. Это позволяет вам различать разные регистраторы и разные обработчики. - person Martijn Pieters; 25.10.2018
comment
@MartijnPieters, моя точка зрения заключалась в том, какой здесь вариант использования? Если бы у нас был один обработчик, было бы недостаточно установить уровень только в корневом регистраторе. Также еще один момент заключается в том, что даже если у вас есть несколько обработчиков, не имеет смысла устанавливать уровень только на уровне обработчика, а не на уровне регистратора. Единственный вариант использования, который я думаю, заключается в том, что уровень настройки на уровне регистратора устанавливает уровень по умолчанию для тех обработчиков, которые делают не устанавливать никакого уровня. - person himanshu219; 26.10.2018
comment
@ himanshu219: вариант использования заключается в том, что как только вы начинаете добавлять несколько обработчиков, вы обычно хотите различать. ОТЛАДКА до консоли, ВНИМАНИЕ и до файла и т. Д. - person Martijn Pieters; 26.10.2018
comment
Вам нужно делать root = logging.getLogger(), если вы используете logging.basicConfig()? - person Lou; 28.01.2021
comment
@Lou: если вы можете настроить желаемую конфигурацию ведения журнала с помощью logging.basicConfig(), тогда нет, вам не нужно получать ссылку на корневой регистратор. - person Martijn Pieters; 30.01.2021
comment
как насчет того, чтобы перехватить вызовы журнала подпроцесса в родительский процесс как переменные? - person Vaidøtas I.; 27.02.2021
comment
@ VaidøtasI. это выходит далеко за рамки этой пары вопрос / ответ и зависит от множества факторов. - person Martijn Pieters; 27.02.2021

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

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
person Eyal    schedule 28.01.2015
comment
Хм, но это не записано в файл, верно? Вопрос заключался в том, как вести журнал в файл и на консоли. - person Weidenrinde; 07.05.2015
comment
Ссылка: Документы Python3: Logging.basicConfig - person Czechnology; 07.11.2017
comment
По крайней мере, в Python 3 похоже, что пропуск stream=sys.stdout по-прежнему работает для меня для входа в консоль. - person Taylor Edmiston; 20.05.2018
comment
@TaylorEdmiston Да, но это поток stderr AFAIK. Попробуйте перенаправить вывод оболочки. - person Sorin; 30.05.2018
comment
OK. Это не отвечает одновременно и на запись в файл, и на консоль, но было приятно найти то, что мне нужно, в 3 строчки или меньше. - person Steve3p0; 23.07.2019

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

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')
person Anton Protopopov    schedule 26.06.2017

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

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

person Alok Singh Mahor    schedule 27.07.2014
comment
Замечательный ответ, хотя и немного запутанный. Мне нравится, как вы показываете, как использовать разные уровни и форматы для потоков и файлов. +1, но +2 по духу. - person The Unfun Cat; 24.06.2015
comment
Для меня это не сработало без параметра sys.stdout в ch = logging.StreamHandler() - person veuncent; 14.10.2019

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

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
person Weidenrinde    schedule 07.05.2015
comment
При этом не отображаются метки INFO, DEBUG и ERROR перед сообщением журнала в консоли. Эти метки отображаются в файле. Есть идеи также показать ярлыки на консоли? - person JahMyst; 20.01.2016
comment
Спасибо, @JahMyst, я добавил Formatter. К сожалению, это уже не такой короткий, но все же самый простой способ. :-) - person Weidenrinde; 22.01.2016

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

import logging
import logging.config
import sys

class _ExcludeErrorsFilter(logging.Filter):
    def filter(self, record):
        """Only returns log messages with log level below ERROR (numeric value: 40)."""
        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)
person Elias Strehle    schedule 12.11.2018
comment
пришлось переименовать регистратор в пустую строку, чтобы фактически получить корневой регистратор. В противном случае очень полезно, спасибо! - person Newtopian; 03.09.2019
comment
эй, никогда раньше не осознавал существование dictConfig !! огромная благодарность !!! - person pepoluan; 30.09.2020

Для более подробных объяснений - отличная документация по этой ссылке. Например: это просто, вам нужно всего лишь настроить два регистратора.

import sys
import logging

logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('my_log_info.log')
sh = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('[%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S')
fh.setFormatter(formatter)
sh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(sh)

def hello_logger():
    logger.info("Hello info")
    logger.critical("Hello critical")
    logger.warning("Hello warning")
    logger.debug("Hello debug")

if __name__ == "__main__":
    print(hello_logger())

Выход - клемма:

[Mon, 10 Aug 2020 12:44:25] INFO [TestLoger.py.hello_logger:15] Hello info
[Mon, 10 Aug 2020 12:44:25] CRITICAL [TestLoger.py.hello_logger:16] Hello critical
[Mon, 10 Aug 2020 12:44:25] WARNING [TestLoger.py.hello_logger:17] Hello warning
[Mon, 10 Aug 2020 12:44:25] DEBUG [TestLoger.py.hello_logger:18] Hello debug
None

Вывод - в файл:

файл входа


ОБНОВЛЕНИЕ: цветной терминал

Упаковка:

pip install colorlog

Код:

import sys
import logging
import colorlog

logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('my_log_info.log')
sh = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('[%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S')
fh.setFormatter(formatter)
sh.setFormatter(colorlog.ColoredFormatter('%(log_color)s [%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S'))
logger.addHandler(fh)
logger.addHandler(sh)

def hello_logger():
    logger.info("Hello info")
    logger.critical("Hello critical")
    logger.warning("Hello warning")
    logger.debug("Hello debug")
    logger.error("Error message")

if __name__ == "__main__":
    hello_logger()

вывод:  введите описание изображения здесь

Рекомендация:

Полная конфигурация регистратора из файла INI, который также включает настройку для stdout и debug.log:

  • handler_file
    • level=WARNING
  • handler_screen
    • level=DEBUG
person Milovan Tomašević    schedule 10.08.2020

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

logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
logging.getLogger().addHandler(logging.StreamHandler())
person Lexander    schedule 23.01.2019

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

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")

Вывод покажет «тестовое сообщение» на стандартном выводе, а также в файле.

person Kiki Jewell    schedule 02.04.2019

Я упростил свой исходный код (исходная версия которого - ООП и использует файл конфигурации), чтобы дать вам альтернативное решение для решения @ EliasStrehle, без использования dictConfig (таким образом, проще всего интегрировать с существующим исходным кодом):

import logging
import sys


def create_stream_handler(stream, formatter, level, message_filter=None):
    handler = logging.StreamHandler(stream=stream)
    handler.setLevel(level)
    handler.setFormatter(formatter)
    if message_filter:
        handler.addFilter(message_filter)
    return handler


def configure_logger(logger: logging.Logger, enable_console: bool = True, enable_file: bool = True):
    if not logger.handlers:
        if enable_console:
            message_format: str = '{asctime:20} {name:16} {levelname:8} {message}'
            date_format: str = '%Y/%m/%d %H:%M:%S'
            level: int = logging.DEBUG
            formatter = logging.Formatter(message_format, date_format, '{')

            # Configures error output (from Warning levels).
            error_output_handler = create_stream_handler(sys.stderr, formatter,
                                                         max(level, logging.WARNING))
            logger.addHandler(error_output_handler)

            # Configures standard output (from configured Level, if lower than Warning,
            #  and excluding everything from Warning and higher).
            if level < logging.WARNING:
                standard_output_filter = lambda record: record.levelno < logging.WARNING
                standard_output_handler = create_stream_handler(sys.stdout, formatter, level,
                                                                standard_output_filter)
                logger.addHandler(standard_output_handler)

        if enable_file:
            message_format: str = '{asctime:20} {name:16} {levelname:8} {message}'
            date_format: str = '%Y/%m/%d %H:%M:%S'
            level: int = logging.DEBUG
            output_file: str = '/tmp/so_test.log'

            handler = logging.FileHandler(output_file)
            formatter = logging.Formatter(message_format, date_format, '{')
            handler.setLevel(level)
            handler.setFormatter(formatter)
            logger.addHandler(handler)

Это очень простой способ проверить это:

logger: logging.Logger = logging.getLogger('MyLogger')
logger.setLevel(logging.DEBUG)
configure_logger(logger, True, True)
logger.debug('Debug message ...')
logger.info('Info message ...')
logger.warning('Warning ...')
logger.error('Error ...')
logger.fatal('Fatal message ...')
person Bsquare ℬℬ    schedule 28.07.2020