日志、StreamHandler和标准流

80 投票
6 回答
105302 浏览
提问于 2025-04-15 14:05

我不知道怎么把信息级别的消息记录到标准输出(stdout),而把其他所有消息记录到标准错误(stderr)。我已经看过这个http://docs.python.org/library/logging.html了。有什么建议吗?

6 个回答

17

因为我的修改被拒绝了,所以我来分享我的答案。@goncalopp的回答不错,但不能单独使用,也不能直接拿来用。下面是我改进后的版本:

import sys, logging


class LogFilter(logging.Filter):
    """Filters (lets through) all messages with level < LEVEL"""
    # http://stackoverflow.com/a/24956305/408556
    def __init__(self, level):
        self.level = level

    def filter(self, record):
        # "<" instead of "<=": since logger.setLevel is inclusive, this should
        # be exclusive
        return record.levelno < self.level

MIN_LEVEL = logging.DEBUG
stdout_hdlr = logging.StreamHandler(sys.stdout)
stderr_hdlr = logging.StreamHandler(sys.stderr)
log_filter = LogFilter(logging.WARNING)
stdout_hdlr.addFilter(log_filter)
stdout_hdlr.setLevel(MIN_LEVEL)
stderr_hdlr.setLevel(max(MIN_LEVEL, logging.WARNING))
# messages lower than WARNING go to stdout
# messages >= WARNING (and >= STDOUT_LOG_LEVEL) go to stderr

rootLogger = logging.getLogger()
rootLogger.addHandler(stdout_hdlr)
rootLogger.addHandler(stderr_hdlr)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Example Usage
>>> logger.debug("A DEBUG message")
>>> logger.info("An INFO message")
>>> logger.warning("A WARNING message")
>>> logger.error("An ERROR message")
>>> logger.critical("A CRITICAL message")
19

一般来说,我觉得把低于 WARNING 级别的消息都转到 标准输出(也就是屏幕上显示的内容),比只转 INFO 消息要更合理。

根据 Vinay Sajip 的精彩回答,我想出了这个:

class MaxLevelFilter(Filter):
    '''Filters (lets through) all messages with level < LEVEL'''
    def __init__(self, level):
        self.level = level

    def filter(self, record):
        return record.levelno < self.level # "<" instead of "<=": since logger.setLevel is inclusive, this should be exclusive


MIN_LEVEL= DEBUG
#...
stdout_hdlr = StreamHandler(sys.stdout)
stderr_hdlr = StreamHandler(sys.stderr)
lower_than_warning= MaxLevelFilter(WARNING)
stdout_hdlr.addFilter( lower_than_warning )     #messages lower than WARNING go to stdout
stdout_hdlr.setLevel( MIN_LEVEL )
stderr_hdlr.setLevel( max(MIN_LEVEL, WARNING) ) #messages >= WARNING ( and >= STDOUT_LOG_LEVEL ) go to stderr
#...
133

下面这个脚本,log1.py

import logging, sys

class SingleLevelFilter(logging.Filter):
    def __init__(self, passlevel, reject):
        self.passlevel = passlevel
        self.reject = reject

    def filter(self, record):
        if self.reject:
            return (record.levelno != self.passlevel)
        else:
            return (record.levelno == self.passlevel)

h1 = logging.StreamHandler(sys.stdout)
f1 = SingleLevelFilter(logging.INFO, False)
h1.addFilter(f1)
rootLogger = logging.getLogger()
rootLogger.addHandler(h1)
h2 = logging.StreamHandler(sys.stderr)
f2 = SingleLevelFilter(logging.INFO, True)
h2.addFilter(f2)
rootLogger.addHandler(h2)
logger = logging.getLogger("my.logger")
logger.setLevel(logging.DEBUG)
logger.debug("A DEBUG message")
logger.info("An INFO message")
logger.warning("A WARNING message")
logger.error("An ERROR message")
logger.critical("A CRITICAL message")

运行后,会产生以下结果。

C:\temp>log1.py
A DEBUG message
An INFO message
A WARNING message
An ERROR message
A CRITICAL message

正如你所预期的,因为在终端中,sys.stdoutsys.stderr 是一样的。现在,我们来把标准输出(stdout)重定向到一个文件,叫做 tmp

C:\temp>log1.py >tmp
A DEBUG message
A WARNING message
An ERROR message
A CRITICAL message

所以,INFO消息没有打印到终端上——但是,指向 sys.stderr 的消息却被打印出来了。我们来看看 tmp 里有什么:

C:\temp>type tmp
An INFO message

所以这个方法似乎达到了你想要的效果。

撰写回答