我正在使用 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")
在这里感谢您的帮助!
DEBUG
,它是否仍会记录到标准输出和文件?我认为只有将其设置为 INFO
。如果我错了,请纠正我。
只需获取根记录器的句柄并添加 StreamHandler
。 StreamHandler
写入标准错误。不确定您是否真的需要 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
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 attributes。 )
上面的设置只需要在脚本开头附近进行一次。您可以稍后使用代码库中所有其他位置的日志记录,如下所示:
logging.info('Useful message')
logging.error('Something bad happened')
...
注意:如果它不起作用,其他人可能已经以不同的方式初始化了日志系统。评论建议在调用 basicConfig()
之前执行 logging.root.handlers = []
。
FileHandler
的定义:logging.FileHandler(filename, mode='a', encoding=None, delay=False)
。这意味着,当您只想登录同一个文件夹时,只需使用 FileHandler("mylog.log")
。如果您想每次都覆盖日志,请将“w”设置为第二个参数。
basicConfig
之前执行 logging.root.handlers = []
,看看函数 - 这很烦人。
noinspection PyArgumentList
来让 PyCharm 开心,因为 youtrack.jetbrains.com/issue/PY-39762
添加不带参数的 StreamHandler 会转到 stderr 而不是 stdout。如果其他进程依赖于标准输出转储(即在编写 NRPE 插件时),请确保明确指定标准输出,否则您可能会遇到一些意想不到的麻烦。
这是一个重用假设值和问题中的 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)
DEBUG
,它是否仍会记录到标准输出和文件?我认为只有将其设置为 INFO
。如果我错了,请纠正我。
这是一个基于 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://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())
关于 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(过滤发送到 stdout 和 stderr 的输出并将转义序列转换为原生 Windows API 调用):
import colorama
colorama.init()
main.py
中设置一次日志记录,然后在模块中设置 import logging
+ logging.debug()
等。不太确定为什么它不适用于您的情况。
在设置任何其他处理程序或记录任何消息之前,使用 stream=sys.stdout
作为参数运行 basicConfig
,或者手动添加将消息推送到标准输出的 StreamHandler
到根记录器(或您想要的任何其他记录器,为此事情)。
stream
和 filename
参数一起使用或与处理程序一起使用时出现错误
使用不同级别和格式记录到 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)
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.')
\n
和 %s
时获取 ValueError: unsupported format character 'n' (0x6e) at index 46
在多个 Python 包中一遍又一遍地使用 Waterboy 的代码后,我最终将其转换为一个小的独立 Python 包,您可以在此处找到它:
https://github.com/acschaefer/duallog
该代码有据可查且易于使用。只需下载 .py
文件并将其包含在您的项目中,或通过 pip install duallog
安装整个包。
我已经通过以下模块(Gist is also available here)同时处理了将日志和打印重定向到磁盘、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()
尽管该问题专门要求 logger 配置,但 还有一种替代方法,不需要对 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')
。
我们也可以将其扩展一点,因此仅在必要时才将 print 改为 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
的任何处理程序,并检查记录器是否已针对指定级别启用。
请注意,这不是optimized是为了提高性能。
对于 2.7,请尝试以下操作:
fh = logging.handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
sys.stdout
初始化StreamHandler
,然后它将记录到那个而不是 stderr。import sys
。并实际初始化处理程序,即consoleHandler = logging.StreamHandler(sys.stdout)
rootLogger.setLevel(logging.DEBUG)