记录器配置用于记录到文件并打印到标准输出
- 2025-02-18 09:23:00
- admin 原创
- 40
问题描述:
我正在使用 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:
只需获取根记录器的句柄并添加StreamHandler
。StreamHandler
写入 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: "[1;35m", # bright/bold magenta
logging.ERROR: "[1;31m", # bright/bold red
logging.WARNING: "[1;33m", # bright/bold yellow
logging.INFO: "[0;37m", # white / light gray
logging.DEBUG: "[1;30m" # bright/bold dark gray
}
RESET_CODE = "[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)
# 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 (过滤发送到stdout和stderr 的输出并将转义序列转换为本机 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()
- 2025年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)