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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13733552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 09:30:18  来源:igfitidea点击:

logger configuration to log to file and print to stdout

pythonfileloggingstdout

提问by stdcerr

I'm using Python's logging module to log some debug strings to a file which works pretty well. Now in addition, I'd like to use this module to also print the strings out to stdout. How do I do this? In order to log my strings to a file I use following code:

我正在使用 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)

and then call a logger function like

然后调用一个记录器函数,如

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

Thank you for some help here!

感谢您在这里提供的帮助!

采纳答案by Waterboy

Just get a handle to the root logger and add the StreamHandler. The StreamHandlerwrites to stderr. Not sure if you really need stdout over stderr, but this is what I use when I setup the Python logger and I also add the FileHandleras well. Then all my logs go to both places (which is what it sounds like you want).

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

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

If you want to output to stdoutinstead of stderr, you just need to specify it to the StreamHandlerconstructor.

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

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

You could also add a Formatterto it so all your log lines have a common header.

您还可以向其添加 aFormatter以便所有日志行都有一个公共标题。

ie:

IE:

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)

Prints to the format of:

打印为以下格式:

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

回答by Silas Ray

Either run basicConfigwith stream=sys.stdoutas the argument prior to setting up any other handlers or logging any messages, or manually add a StreamHandlerthat pushes messages to stdout to the root logger (or any other logger you want, for that matter).

无论是运行basicConfigstream=sys.stdout作为参数之前设立任何其他处理程序或记录任何消息,或手动添加StreamHandler到stdout是推动信息到根logger(或任何你想要的其他记录器,对于这个问题)。

回答by Hazok

Adding a StreamHandler without arguments goes to stderr instead of stdout. If some other process has a dependency on the stdout dump (i.e. when writing an NRPE plugin), then make sure to specify stdout explicitly or you might run into some unexpected troubles.

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

Here's a quick example reusing the assumed values and LOGFILE from the question:

这是重用问题中的假定值和 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)

回答by JonM

For 2.7, try the following:

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

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

回答by Yirkha

logging.basicConfig()can take a keyword argument handlerssince Python 3.3, which simplifies logging setup a lot, especially when setting up multiple handlers with the same formatter:

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

handlers– If specified, this should be an iterable of already created handlers to add to the root logger. Any handlers which don't already have a formatter set will be assigned the default formatter created in this function.

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

The whole setup can therefore be done with a single call like this:

因此,整个设置可以通过这样的单个调用完成:

import logging

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

(Or with import sys+ StreamHandler(sys.stdout)per original question's requirements – the default for StreamHandler is to write to stderr. Look at LogRecord attributesif you want to customize the log format and add things like filename/line, thread info etc.)

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

The setup above needs to be done only once near the beginning of the script. You can use the logging from all other places in the codebase later like this:

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

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

Note: If it doesn't work, someone else has probably already initialized the logging system differently. Comments suggest doing logging.root.handlers = []before the call to basicConfig().

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

回答by Lexxer

After having used Waterboy's code over and over in multiple Python packages, I finally cast it into a tiny standalone Python package, which you can find here:

在多个 Python 包中一遍又一遍地使用 Waterboy 的代码后,我终于将其转换为一个小型的独立 Python 包,您可以在这里找到它:

https://github.com/acschaefer/duallog

https://github.com/acschaefer/duallog

The code is well documented and easy to use. Simply download the .pyfile and include it in your project, or install the whole package via pip install duallog.

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

回答by Andrej Debenjak

Logging to stdoutand rotating filewith different levels and formats:

记录到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.')

回答by Maxxim

Here is a complete, nicely wrapped solution based on Waterboy's answerand various other sources. It supports logging to both console and log file, allows for different log level settings, provides colorized output and is easily configurable (also available as Gist):

这是一个基于Waterboy 的答案和其他各种来源的完整的、包装精美的解决方案。它支持记录到控制台和日志文件,允许不同的日志级别设置,提供彩色输出并且易于配置(也可作为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://stackoverflow.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 colored output
class LogFormatter(logging.Formatter):

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

    RESET_CODE = "3[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())