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

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

谢谢你的帮助!

475217 次浏览

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

只需获得根记录器的句柄并添加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,这样所有日志行都有一个公共标题。

即:

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

添加不带参数的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)

对于2.7,尝试以下操作:

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

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

# EYZ1 mdash;如果指定,这应该是一个已创建的要添加到根日志记录器的处理程序的可迭代对象。任何还没有格式化器集的处理程序将被分配在此函数中创建的默认格式化器。

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

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。如果你想自定义日志格式并添加文件名/行,线程信息等,请查看表明属性。)

上面的设置只需要在脚本的开头执行一次。你可以在代码库的其他地方使用日志,如下所示:

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

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

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

https://github.com/acschaefer/duallog

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

以不同的级别和格式登录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.')

下面是一个基于呆呆的回答和其他各种来源的完整的、包装良好的解决方案。它支持日志记录到控制台和日志文件,允许不同的日志级别设置,提供彩色输出,并且易于配置(也可作为要点):

#!/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 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())

< p > # EYZ0
为了让彩色输出在经典的命令提示符微软视窗系统中工作,还需要一些额外的代码。这并不适用于更新的< < em >终端/ em >应用程序,它支持彩色输出

有两种选择:

使用Python包彩色光(过滤发送到stdoutstderr的输出,并将转义序列转换为本机Windows API调用;适用于Windows XP及以上版本):

import colorama
colorama.init()

使用以下函数启用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

我已经处理了通过以下模块(Gist也可以在这里找到)将日志和打印同时重定向到磁盘、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()


尽管这个问题特别要求使用日志配置,但是还有一种替代方法,它不需要对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

我们现在叫log_and_print(message='something', level=logging.DEBUG, logger=logger)而不是logger.debug('something')

我们也可以稍微扩展一下,所以只有在必要的时候才用打印s到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的处理程序,并检查记录器是否为指定级别启用。

注意,对于性能,已经变成了优化