记录器配置以记录到文件并打印到标准输出

Posted

技术标签:

【中文标题】记录器配置以记录到文件并打印到标准输出【英文标题】:logger configuration to log to file and print to stdout 【发布时间】:2012-11-23 21:18:43 【问题描述】:

我正在使用 Python 的日志记录模块将一些调试字符串记录到一个运行良好的文件中。现在另外,我想使用这个模块将字符串打印到标准输出。我该怎么做呢?为了将我的字符串记录到文件中,我使用以下代码:

import logging
import logging.handlers
logger = logging.getLogger("")
logger.setLevel(logging.DEBUG)
handler = logging.handlers.RotatingFileHandler(
    LOGFILE, maxBytes=(1048576*5), backupCount=7
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

然后调用类似的记录器函数

logger.debug("I am written to the file")

在这里感谢您的帮助!

【问题讨论】:

【参考方案1】:

在设置任何其他处理程序或记录任何消息之前运行basicConfigstream=sys.stdout 作为参数,或者手动添加一个StreamHandler 将消息推送到标准输出到根记录器(或您想要的任何其他记录器,就此而言)。

【讨论】:

当我尝试将 streamfilename 参数一起使用或与处理程序一起使用时出现错误【参考方案2】:

只需获取根记录器的句柄并添加StreamHandlerStreamHandler 写入标准错误。不确定您是否真的需要标准输出而不是标准错误,但这是我在设置 Python 记录器时使用的,我还添加了FileHandler。然后我所有的日志都去这两个地方(这听起来像你想要的)。

import logging
logging.getLogger().addHandler(logging.StreamHandler())

如果你想输出到stdout而不是stderr,你只需要将它指定给StreamHandler构造函数。

import sys
# ...
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))

您还可以在其中添加Formatter,这样您的所有日志行都有一个共同的标题。

即:

import logging
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
rootLogger = logging.getLogger()

fileHandler = logging.FileHandler("0/1.log".format(logPath, fileName))
fileHandler.setFormatter(logFormatter)
rootLogger.addHandler(fileHandler)

consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(logFormatter)
rootLogger.addHandler(consoleHandler)

打印成以下格式:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message

【讨论】:

你也可以只用sys.stdout初始化StreamHandler,然后它会记录到那个而不是stderr。 @sr2222 logger.addHandler(sys.stdout) 给我 NameError: name 'sys' is not defined 嗯,是的......你必须先import sys。并实际初始化处理程序,即consoleHandler = logging.StreamHandler(sys.stdout) 因为正如我已经说过的,这不是你的做法。使用 sys.stdout 创建 HANDLER,然后将处理程序附加到记录器。 如果您想查看信息或调试消息,请不要忘记rootLogger.setLevel(logging.DEBUG)【参考方案3】:

添加不带参数的 StreamHandler 会转到 stderr 而不是 stdout。如果其他进程依赖于 stdout 转储(即在编写 NRPE 插件时),请确保明确指定 stdout,否则您可能会遇到一些意想不到的麻烦。

这是一个重用假设值和问题中的 LOGFILE 的快速示例:

import logging
from logging.handlers import RotatingFileHandler
from logging import handlers
import sys

log = logging.getLogger('')
log.setLevel(logging.DEBUG)
format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(format)
log.addHandler(ch)

fh = handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
fh.setFormatter(format)
log.addHandler(fh)

【讨论】:

【参考方案4】:

对于 2.7,请尝试以下操作:

fh = logging.handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)

【讨论】:

【参考方案5】:

logging.basicConfig() 从 Python 3.3 开始可以使用关键字参数 handlers,这大大简化了日志设置,尤其是在使用相同的格式化程序设置多个处理程序时:

handlers – 如果指定,这应该是已创建的处理程序的可迭代,以添加到根记录器。任何尚未设置格式化程序的处理程序都将被分配在此函数中创建的默认格式化程序。

因此,整个设置可以通过如下调用完成:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("debug.log"),
        logging.StreamHandler()
    ]
)

(或者根据原始问题的要求使用import sys + StreamHandler(sys.stdout) - StreamHandler 的默认设置是写入标准错误。如果您想自定义日志格式并添加文件名/行、线程等内容,请查看LogRecord attributes信息等)

上面的设置只需要在脚本开头附近进行一次。您可以稍后使用代码库中所有其他位置的日志记录,如下所示:

logging.info('Useful message')
logging.error('Something bad happened')
...

注意:如果它不起作用,其他人可能已经以不同的方式初始化了日志系统。评论建议在致电basicConfig() 之前先进行logging.root.handlers = []

【讨论】:

不要忘记设置 level=logging.INFO 或所需的级别 FileHandler 的定义:logging.FileHandler(filename, mode='a', encoding=None, delay=False)。这意味着,当您只想登录同一个文件夹时,您可以使用FileHandler("mylog.log")。如果您想每次都覆盖日志,请将“w”设置为第二个参数。 我试过这个,但输出文件是空的,尽管控制台给出了输出。有什么建议吗..? @Ramesh-X ,这也让我发疯了。只需在调用basicConfig 之前执行logging.root.handlers = [],看看函数——这很烦人。 我还需要 noinspection PyArgumentList 让 PyC​​harm 开心,因为 youtrack.jetbrains.com/issue/PY-39762【参考方案6】:

在多个 Python 包中反复使用 Waterboy 的代码后,我最终将其转换为一个小型独立 Python 包,您可以在此处找到它:

https://github.com/acschaefer/duallog

代码有据可查且易于使用。只需下载.py 文件并将其包含在您的项目中,或通过pip install duallog 安装整个包。

【讨论】:

由于某种原因既没有记录到控制台,也没有文件(为空)【参考方案7】:

以不同的级别和格式登录到stdoutrotating file

import logging
import logging.handlers
import sys

if __name__ == "__main__":

    # Change root logger level from WARNING (default) to NOTSET in order for all messages to be delegated.
    logging.getLogger().setLevel(logging.NOTSET)

    # Add stdout handler, with level INFO
    console = logging.StreamHandler(sys.stdout)
    console.setLevel(logging.INFO)
    formater = logging.Formatter('%(name)-13s: %(levelname)-8s %(message)s')
    console.setFormatter(formater)
    logging.getLogger().addHandler(console)

    # Add file rotating handler, with level DEBUG
    rotatingHandler = logging.handlers.RotatingFileHandler(filename='rotating.log', maxBytes=1000, backupCount=5)
    rotatingHandler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rotatingHandler.setFormatter(formatter)
    logging.getLogger().addHandler(rotatingHandler)

    log = logging.getLogger("app." + __name__)

    log.debug('Debug message, should only appear in the file.')
    log.info('Info message, should appear in file and stdout.')
    log.warning('Warning message, should appear in file and stdout.')
    log.error('Error message, should appear in file and stdout.')

【讨论】:

【参考方案8】:

这是一个基于Waterboy's answer 和其他各种来源的完整、包装精美的解决方案。它支持记录到控制台和日志文件,允许不同的日志级别设置,提供彩色输出并且易于配置(也可作为 Gist 使用):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------------
#                                                                               -
#  Python dual-logging setup (console and log file),                            -
#  supporting different log levels and colorized output                         -
#                                                                               -
#  Created by Fonic <https://github.com/fonic>                                  -
#  Date: 04/05/20                                                               -
#                                                                               -
#  Based on:                                                                    -
#  https://***.com/a/13733863/1976617                                 -
#  https://uran198.github.io/en/python/2016/07/12/colorful-python-logging.html  -
#  https://en.wikipedia.org/wiki/ANSI_escape_code#Colors                        -
#                                                                               -
# -------------------------------------------------------------------------------

# Imports
import os
import sys
import logging

# Logging formatter supporting colorized output
class LogFormatter(logging.Formatter):

    COLOR_CODES = 
        logging.CRITICAL: "\033[1;35m", # bright/bold magenta
        logging.ERROR:    "\033[1;31m", # bright/bold red
        logging.WARNING:  "\033[1;33m", # bright/bold yellow
        logging.INFO:     "\033[0;37m", # white / light gray
        logging.DEBUG:    "\033[1;30m"  # bright/bold black / dark gray
    

    RESET_CODE = "\033[0m"

    def __init__(self, color, *args, **kwargs):
        super(LogFormatter, self).__init__(*args, **kwargs)
        self.color = color

    def format(self, record, *args, **kwargs):
        if (self.color == True and record.levelno in self.COLOR_CODES):
            record.color_on  = self.COLOR_CODES[record.levelno]
            record.color_off = self.RESET_CODE
        else:
            record.color_on  = ""
            record.color_off = ""
        return super(LogFormatter, self).format(record, *args, **kwargs)

# Setup logging
def setup_logging(console_log_output, console_log_level, console_log_color, logfile_file, logfile_log_level, logfile_log_color, log_line_template):

    # Create logger
    # For simplicity, we use the root logger, i.e. call 'logging.getLogger()'
    # without name argument. This way we can simply use module methods for
    # for logging throughout the script. An alternative would be exporting
    # the logger, i.e. 'global logger; logger = logging.getLogger("<name>")'
    logger = logging.getLogger()

    # Set global log level to 'debug' (required for handler levels to work)
    logger.setLevel(logging.DEBUG)

    # Create console handler
    console_log_output = console_log_output.lower()
    if (console_log_output == "stdout"):
        console_log_output = sys.stdout
    elif (console_log_output == "stderr"):
        console_log_output = sys.stderr
    else:
        print("Failed to set console output: invalid output: '%s'" % console_log_output)
        return False
    console_handler = logging.StreamHandler(console_log_output)

    # Set console log level
    try:
        console_handler.setLevel(console_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set console log level: invalid level: '%s'" % console_log_level)
        return False

    # Create and set formatter, add console handler to logger
    console_formatter = LogFormatter(fmt=log_line_template, color=console_log_color)
    console_handler.setFormatter(console_formatter)
    logger.addHandler(console_handler)

    # Create log file handler
    try:
        logfile_handler = logging.FileHandler(logfile_file)
    except Exception as exception:
        print("Failed to set up log file: %s" % str(exception))
        return False

    # Set log file log level
    try:
        logfile_handler.setLevel(logfile_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set log file log level: invalid level: '%s'" % logfile_log_level)
        return False

    # Create and set formatter, add log file handler to logger
    logfile_formatter = LogFormatter(fmt=log_line_template, color=logfile_log_color)
    logfile_handler.setFormatter(logfile_formatter)
    logger.addHandler(logfile_handler)

    # Success
    return True

# Main function
def main():

    # Setup logging
    script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    if (not setup_logging(console_log_output="stdout", console_log_level="warning", console_log_color=True,
                        logfile_file=script_name + ".log", logfile_log_level="debug", logfile_log_color=False,
                        log_line_template="%(color_on)s[%(created)d] [%(threadName)s] [%(levelname)-8s] %(message)s%(color_off)s")):
        print("Failed to setup logging, aborting.")
        return 1

    # Log some messages
    logging.debug("Debug message")
    logging.info("Info message")
    logging.warning("Warning message")
    logging.error("Error message")
    logging.critical("Critical message")

# Call main function
if (__name__ == "__main__"):
    sys.exit(main())

关于 Microsoft Windows 的注意事项: 要使颜色真正出现在 Microsoft Windows 上,还需要执行其他步骤。有两种选择(均在 Microsoft Windows 10 上成功测试):

1) 使用以下代码启用 ANSI 终端模式(通过设置标志 ENABLE_VIRTUAL_TERMINAL_PROCESSING 使终端能够解释转义序列;有关此 here、here、here 和 here 的更多信息):

# Enable ANSI terminal mode on Microsoft Windows
def windows_enable_ansi_terminal_mode():
    if (sys.platform != "win32"):
        return None
    try:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        result = kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
        if (result == 0): raise Exception
        return True
    except:
        return False

2) 使用 Python 包 colorama(过滤发送到 stdoutstderr 的输出并将转义序列转换为原生 Windows API 调用):

import colorama
colorama.init()

【讨论】:

在 Windows 终端中,此代码不会显示颜色。如果您希望它们出现在 Windows 终端中,只需“导入 colorama”即可。 @bitfhacker:是的,但这与我的代码无关。在 Windows 10 上,您必须先启用 ANSI 终端模式。可悲的是,Python 本身在启动时并没有这样做。我添加了一个功能来启用它,我已经成功使用了一段时间了。 这在 Win10 上对我有用:***.com/questions/9848889/… --> "from colorama import init" 并在 "LogFormatter(loggin.Formatter) 之后的以下行中使用 "init(convert=True)": "

以上是关于记录器配置以记录到文件并打印到标准输出的主要内容,如果未能解决你的问题,请参考以下文章

为啥打印到标准输出这么慢?可以提速吗?

使用 C/C++,如何以彩色打印到标准输出,但前提是终端支持它?

aplay 管道使用文件而不是标准输入和标准输出来记录

C 低级标准输入以接受文件名,然后将文件内容打印到标准输出

R:同时重定向到标准输出和动态文件

NodeJS execSync 与自定义标准输出流,用于收集输出并即时记录到终端