使Python记录器将所有消息输出到标准输出(stdout),除了日志文件。

697
有没有一种方法可以让Python记录器(使用logging模块)自动将输出内容发送到标准输出流(stdout),除了它们应该去的日志文件之外?例如,我想要所有对logger.warninglogger.criticallogger.error的调用都去它们预定的位置,但是同时也总是被复制到stdout。这是为了避免重复消息的产生,比如:
mylogger.critical("something failed")
print("something failed")

2
请查看此答案 https://dev59.com/CGox5IYBdhLWcg3wQSOp - SeF
11个回答

907

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

12
好的,如果已经将内容重定向到文件,如何同时将其打印到“stdout”中呢? - user248237
78
通过添加一个新的处理程序,如图所示。新的处理程序不会替换现有的处理程序,它们也可以处理日志记录条目。 - Martijn Pieters
7
@PrakharMohanSrivastava 我猜你只需要将它添加到传递给 logging.Formatter 的字符串中即可。 - A.Wan
3
记录器有一个级别,处理程序也有一个级别。记录器将处理该级别及更高级别的消息,而处理程序将处理该级别及更高级别的消息。这让您可以区分不同的记录器和不同的处理程序。 - Martijn Pieters
8
使用情况是,一旦开始添加多个处理程序,通常希望进行区分。将DEBUG信息输出到控制台,将WARNING及以上信息输出到文件等。 - Martijn Pieters
显示剩余10条评论

802

使用basicConfig最简单的方式是将日志记录到标准输出:

import logging
import sys

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

91
哦,但这个并没有被记录到文件中,对吧?问题是如何同时将日志记录到文件和控制台。 - Weidenrinde
7
至少在Python 3中,似乎省略stream=sys.stdout对我来说仍适用于将日志记录到控制台。 - Taylor D. Edmiston
6
据我所知,这是标准错误输出流。尝试从 shell 重定向输出。 - Sorin
3
好的。这并没有回答如何同时将日志记录到文件和控制台,但在不到3行代码里找到了所需内容还是很不错的。 - Steve3p0
现在有点晚了,而且我对日志记录不是很专业,但我认为我们可以使用 with open('logfile.txt', 'a+', encoding='utf-8') as f: 将任何内容写入文件,然后使用 print(logging ..., file=f) - Ali Abdi
显示剩余3条评论

150

你可以为文件和标准输出创建两个处理程序,然后使用handlers参数创建一个记录器,并传递给basicConfig。如果两个处理程序具有相同的日志级别和格式输出,这将非常有用:

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

83

可以使用多个处理程序。

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

5
非常好的回答,尽管有点凌乱。喜欢你展示如何使用不同级别和格式的流和文件。+1,但精神上是+2。 - The Unfun Cat
1
对我来说,在 ch = logging.StreamHandler() 中没有 sys.stdout 参数是无法正常工作的。 - veuncent

44
这是一种基于强大但文档较差的 logging.config.dictConfig 方法的解决方案。 它不会将每个日志消息发送到 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)

1
不得不将记录器重命名为空字符串才能实际获取根记录器。否则非常有帮助,谢谢! - Newtopian
3
哇,以前从未意识到 dictConfig 的存在!!非常感激!!! - pepoluan
支持dictConfig使用,可以更轻松地从配置文件加载日志记录。 - rkachach
@rkachach 将日志记录器配置存储在yaml文件中的想法似乎更加健壮,因为它允许代码重用。 - ivan866
这就是Django日志设置的方式,它允许进行一些精细和强大的日志配置... - miller the gorilla

39

将日志同时记录到文件和stderr的最简单方法:

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
@JahMyst,您需要更改根记录器的格式化程序的“format”参数。 - ivan866

32

如需更详细的解释,请参阅链接中的文档。

例如:只需设置两个记录器即可轻松实现。

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文件完整地配置日志记录器,其中还包括对stdoutdebug.log的设置:

  • handler_file
    • level=WARNING
  • handler_screen
    • level=DEBUG

1
优秀的回答。这应该在顶部。 - Omrii

16

既然没有人分享简短的两行代码,那我来分享我的:

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

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

输出将会在stdout和文件中显示"test msg"。最初的回答。

1
你应该使用tee。配置应用程序将输出写入stdout,并运行。
python3 app.py | tee log.txt

然后您将在stdout和log.txt中获得日志消息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接