我使用的是一个Python库(用C++编写(,它在控制台中输出大量消息。不幸的是,该库是封闭源代码的,因此无法更改其行为。我的目标是编写一个Python上下文处理程序,将库C++输出(stdout和stderr(重定向到两个文件中,同时保持Python输出不变(显示在控制台中(。到目前为止,我成功地做到了这一点:
import os
import sys
from pathlib import Path
log_dir = Path('./')
class logSaver():
def __init__(self, logname):
self.logname = logname
sys.stdout.flush()
sys.stderr.flush()
if self.logname == None:
self.logpath_out = os.devnull
self.logpath_err = os.devnull
else:
self.logpath_out = log_dir / (logname + "_out.log")
self.logpath_err = log_dir / (logname + "_err.log")
self.logfile_out = os.open(self.logpath_out, os.O_WRONLY|os.O_TRUNC|os.O_CREAT)
self.logfile_err = os.open(self.logpath_err, os.O_WRONLY|os.O_TRUNC|os.O_CREAT)
def __enter__(self):
self.orig_stdout = sys.stdout # save original stdout
self.orig_stderr = sys.stderr # save original stderr
self.new_stdout = os.dup(1)
self.new_stderr = os.dup(2)
os.dup2(self.logfile_out, 1)
os.dup2(self.logfile_err, 2)
sys.stdout = os.fdopen(self.new_stdout, 'w')
sys.stderr = os.fdopen(self.new_stderr, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.flush()
sys.stderr.flush()
sys.stdout = self.orig_stdout # restore original stdout
sys.stderr = self.orig_stderr # restore original stderr
os.close(self.logfile_out)
os.close(self.logfile_err)
然后按如下方式使用上下文处理程序:
with logSaver("log_filename"):
some_cpp_and_python_code()
结果是创建了两个文件:
- log_filename_out.log
- 日志文件名_err.log
包含C++库生成的所有输出,而Python生成的输出定期显示在控制台中,不会写入日志文件。
该代码运行良好,只重定向C++stdout,同时保持Pythonprint()
调用不变,但是,由于某种原因,它只运行一次:上下文处理程序的所有后续使用都会导致Pythonprint()
调用的中断行为,因为它们不会显示在任何位置(控制台或日志文件(。我怀疑问题可能在于对原始stdout和stderr的错误恢复。
如何更改代码以修复这种行为?
提前非常感谢
感谢@SergeyA的评论,我成功地解决了这个问题。以下是感兴趣的人的最终工作代码:
class logSaver():
def __init__(self, logname):
self.logname = logname
sys.stdout.flush()
sys.stderr.flush()
if self.logname == None:
self.logpath_out = os.devnull
self.logpath_err = os.devnull
else:
self.logpath_out = logname + "_out.log"
self.logpath_err = logname + "_err.log"
self.logfile_out = os.open(self.logpath_out, os.O_WRONLY|os.O_TRUNC|os.O_CREAT)
self.logfile_err = os.open(self.logpath_err, os.O_WRONLY|os.O_TRUNC|os.O_CREAT)
def __enter__(self):
self.orig_stdout = os.dup(1)
self.orig_stderr = os.dup(2)
self.new_stdout = os.dup(1)
self.new_stderr = os.dup(2)
os.dup2(self.logfile_out, 1)
os.dup2(self.logfile_err, 2)
sys.stdout = os.fdopen(self.new_stdout, 'w')
sys.stderr = os.fdopen(self.new_stderr, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.flush()
sys.stderr.flush()
os.dup2(self.orig_stdout, 1)
os.dup2(self.orig_stderr, 2)
os.close(self.orig_stdout)
os.close(self.orig_stderr)
os.close(self.logfile_out)
os.close(self.logfile_err)
问题是我正在用恢复原始流
sys.stdout = self.orig_stdout
而正确的方法是:
os.dup2(self.orig_stdout, 1)
这段代码对我来说有点复杂。这是我自己的版本,删除了一些无用且难以理解的代码,尤其是在__enter__
块中。
我把这个代码放在注释中,只留下看起来有用的代码。
最后,我添加了一个代码的示例测试(针对stdout和stderr(。
# first let's explain that 1 = sys.stdout.fileno()
# 2 = sys.stderr.fileno()
import sys
import os
class logSaver():
def __init__(self, logname=None): # set default to None
self.logname = logname
sys.stdout.flush() # flushes original stdout
sys.stderr.flush() # flushes original stderr
if self.logname == None:
self.logpath_out = os.devnull
self.logpath_err = os.devnull
else:
self.logpath_out = logname + "_out.log"
self.logpath_err = logname + "_err.log"
self.logfile_out = os.open(self.logpath_out, os.O_WRONLY|os.O_TRUNC|os.O_CREAT) # Attention: will not append
self.logfile_err = os.open(self.logpath_err, os.O_WRONLY|os.O_TRUNC|os.O_CREAT) # Attention: will not append
self.orig_stdout = os.dup(1) # creates a new file_descriptor to original stdout
self.orig_stderr = os.dup(2) # creates a new file_descriptor to original stderr
def __enter__(self):
# self.new_stdout = os.dup(1) # why do you need a new_stdout? I don't get it
# self.new_stderr = os.dup(2) # why do you need a new_stderr? I don't get it
os.dup2(self.logfile_out, 1) # stdout file descriptor points to logfile_out
os.dup2(self.logfile_err, 2) # stderr file descriptor points to logfile_err
# sys.stdout = os.fdopen(self.new_stdout, 'w') # why would you do that? target file already opened and pointed to
# sys.stderr = os.fdopen(self.new_stderr, 'w') # why would you do that? target file already opened and pointed to
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.flush() # flushes logfile_out
sys.stderr.flush() # flushes logfile_err
os.dup2(self.orig_stdout, 1) # stdout file descriptor points to original stdout
os.dup2(self.orig_stderr, 2) # stderr file descriptor points to original stdout
os.close(self.orig_stdout) # close duplicate file descriptor to original stdout
os.close(self.orig_stderr) # close duplicate file descriptor to original stderr
os.close(self.logfile_out) # close log file logfile_out
os.close(self.logfile_err) # close log file logfile_err
if __name__ == "__main__":
print("first") # logs to standard stdout
os.write(2,"first stderrn".encode()) # logs to standard stderr
with logSaver("test"):
print("here 1") # logs to file "test_out.log"
os.write(2,"here 1 stderrn".encode()) # logs to file "test_err.log"
print("there 1") # logs to standard stdout
os.write(2,"there 1 stderrn".encode()) # logs to standard stderr
with logSaver("test"):
print("here 2") # delete previous content of file "test_out.log", and logs to it.
os.write(2,"here 2 stderrn".encode()) # delete previous content of file "test_err.log", and logs to it.
print("there 2") # logs to standard stdout
os.write(2,"there 2 stderrn".encode()) # logs to standard stderr