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

2025-02-18 09:23:00
admin
原创
42
摘要:问题描述:我正在使用 Python 的日志模块将一些调试字符串记录到一个文件中,效果很好。现在,此外,我还想使用此模块将字符串打印到 stdout。我该怎么做?为了将我的字符串记录到一个文件中,我使用以下代码:import logging import logging.handlers logger = lo...

问题描述:

我正在使用 Python 的日志模块将一些调试字符串记录到一个文件中,效果很好。现在,此外,我还想使用此模块将字符串打印到 stdout。我该怎么做?为了将我的字符串记录到一个文件中,我使用以下代码:

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:

只需获取根记录器的句柄并添加StreamHandlerStreamHandler写入 stderr。不确定您是否真的需要 stdout 而不是 stderr,但这是我在设置 Python 记录器时使用的,并且我还添加了FileHandler。然后我的所有日​​志都会发送到两个地方(这听起来就是您想要的)。

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

如果要输出为stdout而不是stderr,只需将其指定给StreamHandler构造函数即可。

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

您还可以添加一个Formatter,以便所有日志行都有一个共同的标题。

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)

打印格式为:

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

解决方案 2:

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 的默认设置是写入 stderr。如果您想自定义日志格式并添加文件名/行、线程信息等内容,请查看LogRecord 属性。)

上述设置只需在脚本开头附近进行一次。稍后,您可以像这样在代码库的所有其他地方使用日志记录:

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

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

解决方案 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:

这是一个完整的、包装精良的解决方案,基于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 - 02/07/23                                                    -
#                                                                               -
#  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 colorized output
class LogFormatter(logging.Formatter):

    COLOR_CODES = {
        logging.CRITICAL: "", # bright/bold magenta
        logging.ERROR:    "", # bright/bold red
        logging.WARNING:  "", # bright/bold yellow
        logging.INFO:     "", # white / light gray
        logging.DEBUG:    ""  # bright/bold dark gray
    }

    RESET_CODE = ""

    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)

# Set up logging
def set_up_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():

    # Set up logging
    script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    if (not set_up_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 set up 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的经典命令提示符

中工作,需要一些额外的代码。这不适用于支持开箱即用的彩色输出的较新的终端应用程序。

有两个选项:

1)使用 Python 包colorama (过滤发送到stdoutstderr 的输出并将转义序列转换为本机 Windows API 调用;适用于 Windows XP 及更高版本):

import colorama
colorama.init()

2)使用以下功能启用 ANSI 终端模式(通过设置标志使终端能够解释转义序列ENABLE_VIRTUAL_TERMINAL_PROCESSING;更多信息请参见这里、这里、这里和这里;适用于 Windows 10 及更高版本):

# Imports
import sys
import ctypes

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

解决方案 5:

在设置任何其他处理程序或记录任何消息之前,要么以参数形式运行basicConfig,要么手动添加将消息推送到 stdout 的根记录器(或您想要的任何其他记录器)。stream=sys.stdout`StreamHandler`

解决方案 6:

stdout使用rotating 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)
    formatter = logging.Formatter('%(name)-13s: %(levelname)-8s %(message)s')
    console.setFormatter(formatter)
    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.')

解决方案 7:

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

https://github.com/acschaefer/duallog

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

解决方案 8:

尽管问题特别要求记录器配置,但还有一种替代方法,它不需要对logging配置进行任何更改,也不需要重定向stdout

可能有点简单,但它确实有效:

def log_and_print(message: str, level: int, logger: logging.Logger):
    logger.log(level=level, msg=message)  # log as normal
    print(message)  # prints to stdout by default

例如,logger.debug('something')我们现在称之为log_and_print(message='something', level=logging.DEBUG, logger=logger)

我们还可以稍微扩展一下,这样它只在必要时才打印stdout

def log_print(message: str, level: int, logger: logging.Logger):
    # log the message normally
    logger.log(level=level, msg=message)
    # only print to stdout if the message is not logged to stdout
    msg_logged_to_stdout = False
    current_logger = logger
    while current_logger and not msg_logged_to_stdout:
        is_enabled = current_logger.isEnabledFor(level)
        logs_to_stdout = any(
            getattr(handler, 'stream', None) == sys.stdout
            for handler in current_logger.handlers
        )
        msg_logged_to_stdout = is_enabled and logs_to_stdout
        if not current_logger.propagate:
            current_logger = None
        else:
            current_logger = current_logger.parent            
    if not msg_logged_to_stdout:
        print(message)
    

这将检查记录器及其父级中是否有任何处理程序流式传输到stdout,并检查记录器是否启用了指定的级别。

请注意,这尚未针对性能进行优化。

解决方案 9:

我已经通过以下模块同时将日志和打印重定向到磁盘、stdout 和 stderr 上的文件(要点也可在此处获得):

import logging
import pathlib
import sys

from ml.common.const import LOG_DIR_PATH, ML_DIR


def create_log_file_path(file_path, root_dir=ML_DIR, log_dir=LOG_DIR_PATH):
    path_parts = list(pathlib.Path(file_path).parts)
    relative_path_parts = path_parts[path_parts.index(root_dir) + 1:]
    log_file_path = pathlib.Path(log_dir, *relative_path_parts)
    log_file_path = log_file_path.with_suffix('.log')
    # Create the directories and the file itself
    log_file_path.parent.mkdir(parents=True, exist_ok=True)
    log_file_path.touch(exist_ok=True)
    return log_file_path


def set_up_logs(file_path, mode='a', level=logging.INFO):
    log_file_path = create_log_file_path(file_path)
    logging_handlers = [logging.FileHandler(log_file_path, mode=mode),
                        logging.StreamHandler(sys.stdout)]
    logging.basicConfig(
        handlers=logging_handlers,
        format='%(asctime)s %(name)s %(levelname)s %(message)s',
        level=level
    )


class OpenedFileHandler(logging.FileHandler):

    def __init__(self, file_handle, filename, mode):
        self.file_handle = file_handle
        super(OpenedFileHandler, self).__init__(filename, mode)

    def _open(self):
        return self.file_handle


class StandardError:
    def __init__(self, buffer_stderr, buffer_file):
        self.buffer_stderr = buffer_stderr
        self.buffer_file = buffer_file

    def write(self, message):
        self.buffer_stderr.write(message)
        self.buffer_file.write(message)


class StandardOutput:
    def __init__(self, buffer_stdout, buffer_file):
        self.buffer_stdout = buffer_stdout
        self.buffer_file = buffer_file

    def write(self, message):
        self.buffer_stdout.write(message)
        self.buffer_file.write(message)


class Logger:
    def __init__(self, file_path, mode='a', level=logging.INFO):
        self.stdout_ = sys.stdout
        self.stderr_ = sys.stderr

        log_file_path = create_log_file_path(file_path)
        self.file_ = open(log_file_path, mode=mode)

        logging_handlers = [OpenedFileHandler(self.file_, log_file_path,
                                              mode=mode),
                            logging.StreamHandler(sys.stdout)]
        logging.basicConfig(
            handlers=logging_handlers,
            format='%(asctime)s %(name)s %(levelname)s %(message)s',
            level=level
        )

    # Overrides write() method of stdout and stderr buffers
    def write(self, message):
        self.stdout_.write(message)
        self.stderr_.write(message)
        self.file_.write(message)

    def flush(self):
        pass

    def __enter__(self):
        sys.stdout = StandardOutput(self.stdout_, self.file_)
        sys.stderr = StandardError(self.stderr_, self.file_)

    def __exit__(self, exc_type, exc_val, exc_tb):
        sys.stdout = self.stdout_
        sys.stderr = self.stderr_
        self.file_.close()

作为上下文管理器编写,您可以通过添加额外的行来简单地将功能添加到您的python脚本中:

from logger import Logger

...

if __name__ == '__main__':
    with Logger(__file__):
        main()

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1325  
  IPD(Integrated Product Development)流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。它涵盖了从产品概念产生到产品退市的整个生命周期,通过整合跨部门团队、优化流程等方式,显著提升产品开发的效率和质量,进而为项目的成功奠定坚实基础。深入探究IPD流程的五个阶段与项目成功之间...
IPD流程分为几个阶段   4  
  华为作为全球知名的科技企业,其成功背后的管理体系备受关注。IPD(集成产品开发)流程作为华为核心的产品开发管理模式,其中的创新管理与实践更是蕴含着丰富的经验和深刻的智慧,对众多企业具有重要的借鉴意义。IPD流程的核心架构IPD流程旨在打破部门墙,实现跨部门的高效协作,将产品开发视为一个整体的流程。它涵盖了从市场需求分析...
华为IPD是什么   3  
  IPD(Integrated Product Development)研发管理体系作为一种先进的产品开发模式,在众多企业的发展历程中发挥了至关重要的作用。它不仅仅是一套流程,更是一种理念,一种能够全方位提升企业竞争力,推动企业持续发展的有效工具。深入探究IPD研发管理体系如何助力企业持续发展,对于众多渴望在市场中立足并...
IPD管理流程   3  
  IPD(Integrated Product Development)流程管理旨在通过整合产品开发流程、团队和资源,实现产品的快速、高质量交付。在这一过程中,有效降低成本是企业提升竞争力的关键。通过优化IPD流程管理中的各个环节,可以在不牺牲产品质量和性能的前提下,实现成本的显著降低,为企业创造更大的价值。优化产品规划...
IPD流程分为几个阶段   4  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用