ChatGPT解决这个技术问题 Extra ChatGPT

使 Python 记录器将除日志文件之外的所有消息输出到标准输出

有没有办法让 Python 使用 logging 模块自动将内容输出到标准输出 到它们应该去的日志文件?例如,我希望对 logger.warninglogger.criticallogger.error 的所有调用都转到它们的预期位置,但另外总是复制到 stdout。这是为了避免重复消息,例如:

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

M
Martijn Pieters

所有日志记录输出都由处理程序处理;只需将 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)

很好,但如果它已经被重定向到一个文件,我怎么能把它打印到 stdout
@user248237:通过添加一个新的处理程序,如图所示。新的处理程序不会替换现有的处理程序,它们还可以处理日志条目。
@PrakharMohanSrivastava 我猜你可以将它添加到传递给 logging.Formatter 的字符串中。
@himanshu219:记录器有一个级别,处理程序有一个级别。 logger 将处理该级别及更高级别的消息,handler 将处理该级别及更高级别的消息。它使您可以区分不同的记录器和不同的处理程序。
@himanshu219:用例是,一旦您开始添加多个处理程序,您通常想要区分。 DEBUG 到控制台,WARNING 到一个文件等等。
S
Stevoisiak

记录到标准输出的最简单方法:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

嗯,但这没有记录到文件中,对吧?问题是如何记录到文件和控制台。
至少在 Python 3 中,看起来省略 stream=sys.stdout 仍然可以为我登录到控制台。
@TaylorEdmiston 是的,但它是标准错误流 AFAIK。尝试从 shell 重定向输出。
好的。这不能同时回答:记录到文件和控制台,但很高兴在 3 行或更少的行中找到我需要的内容。
M
Maor Refaeli

您可以为文件和标准输出创建两个处理程序,然后创建一个将 handlers 参数设置为 basicConfig 的记录器。如果两个处理程序的 log_level 和格式输出相同,这可能会很有用:

import logging
import sys

file_handler = logging.FileHandler(filename='tmp.log')
stdout_handler = logging.StreamHandler(stream=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')

v
vallentin

可以使用多个处理程序。

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


精彩的回答,虽然有点乱。喜欢你如何展示如何为流和文件使用不同的级别和格式。 +1,但精神上+2。
对我来说,如果没有 ch = logging.StreamHandler() 中的 sys.stdout 参数,这将不起作用
S
Stevoisiak

记录到文件和标准错误的最简单方法:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)

这不会在控制台中的日志消息之前显示标签 INFO、DEBUG 和 ERROR。它确实在文件中显示了这些标签。在控制台中也显示标签的任何想法?
谢谢@JahMyst,我添加了格式化程序。不幸的是,它不再那么短了,但仍然是最简单的方法。 :-)
r
rkachach

这是一个基于功能强大但记录不充分的 logging.config.dictConfig method 的解决方案。它不是将每条日志消息都发送到 stdout,而是将日志级别为 ERROR 和更高级别的消息发送到 stderr,将其他所有消息发送到 stdout。如果系统的其他部分正在收听 stderrstdout,这将很有用。

import logging
import logging.config
import sys

class _ExcludeErrorsFilter(logging.Filter):
    def filter(self, record):
        """Only lets through log messages with log level below ERROR ."""
        return record.levelno < logging.ERROR


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)

必须将记录器重命名为空字符串才能实际获取根记录器。否则非常有帮助,谢谢!
哇,以前从未意识到 dictConfig 的存在!非常感谢!!!
使用 dictConfig 可以更轻松地从配置文件加载日志记录
M
Milovan Tomašević

如需更详细的说明 - 该 link 上有很好的文档。例如:很简单,你只需要设置两个记录器。

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

输出 - 在文件中:

https://i.stack.imgur.com/edeFh.png

更新:彩色终端

包裹:

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

https://i.stack.imgur.com/Q5gAi.png

推荐:

INI 文件中的 Complete logger configuration,其中还包括 stdoutdebug.log 的设置:

handler_file 级别=警告

级别=警告

handler_screen 级别=调试

级别=调试


L
Lexander

由于没有人共享一个整洁的两个班轮,我将分享我自己的:

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

K
Kiki Jewell

这是一个非常简单的例子:

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”。


B
Bsquare ℬℬ

我简化了我的源代码(其原始版本是 OOP 并使用配置文件),为您提供@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 ...')