Python:将日志与wx结合,使日志流重定向到stdout/stderr框架

3 投票
2 回答
1835 浏览
提问于 2025-04-15 21:54

事情是这样的:

我正在尝试把日志模块和 wx.App() 的重定向功能结合起来。我想要同时把日志记录到一个文件里标准错误输出(stderr)。但是我希望标准错误输出/标准输出(stdout)能重定向到一个单独的窗口,这正是 wx.App 的功能。

我的测试代码是:

import logging
import wx

class MyFrame(wx.Frame):
    def __init__(self):
        self.logger = logging.getLogger("main.MyFrame")
        wx.Frame.__init__(self, parent = None, id = wx.ID_ANY, title = "MyFrame")
        self.logger.debug("MyFrame.__init__() called.")

    def OnExit(self):
        self.logger.debug("MyFrame.OnExit() called.")

class MyApp(wx.App):
    def __init__(self, redirect):
        self.logger = logging.getLogger("main.MyApp")
        wx.App.__init__(self, redirect = redirect)
        self.logger.debug("MyApp.__init__() called.")

    def OnInit(self):
        self.frame = MyFrame()
        self.frame.Show()
        self.SetTopWindow(self.frame)
        self.logger.debug("MyApp.OnInit() called.")
        return True

    def OnExit(self):
        self.logger.debug("MyApp.OnExit() called.")

def main():
    logger_formatter = logging.Formatter("%(name)s\t%(levelname)s\t%(message)s")
    logger_stream_handler = logging.StreamHandler()
    logger_stream_handler.setLevel(logging.INFO)
    logger_stream_handler.setFormatter(logger_formatter)
    logger_file_handler = logging.FileHandler("test.log", mode = "w")
    logger_file_handler.setLevel(logging.DEBUG)
    logger_file_handler.setFormatter(logger_formatter)
    logger = logging.getLogger("main")
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logger_stream_handler)
    logger.addHandler(logger_file_handler)
    logger.info("Logger configured.")

    app = MyApp(redirect = True)
    logger.debug("Created instance of MyApp. Calling MainLoop().")
    app.MainLoop()
    logger.debug("MainLoop() ended.")
    logger.info("Exiting program.")

    return 0

if (__name__ == "__main__"):
    main()

我期望的效果是:
- 创建一个名为 test.log 的文件
- 文件里包含 DEBUG 级别和 INFO/ERROR/WARNING/CRITICAL 级别的日志信息
- INFO 和 ERROR/WARNING/CRITICAL 类型的信息要么显示在控制台上,要么在一个单独的窗口中,具体取决于它们是在哪里生成的
- 不在 MyApp 或 MyFrame 内部的日志信息会显示在控制台上
- 在 MyApp 或 MyFrame 内部的日志信息会显示在一个单独的窗口中

实际效果是:
- 文件被创建,内容是:

main    INFO    Logger configured.
main.MyFrame    DEBUG   MyFrame.__init__() called.
main.MyFrame    INFO    MyFrame.__init__() called.
main.MyApp  DEBUG   MyApp.OnInit() called.
main.MyApp  INFO    MyApp.OnInit() called.
main.MyApp  DEBUG   MyApp.__init__() called.
main    DEBUG   Created instance of MyApp. Calling MainLoop().
main.MyApp  DEBUG   MyApp.OnExit() called.
main    DEBUG   MainLoop() ended.
main    INFO    Exiting program.

- 控制台输出是:

main    INFO    Logger configured.
main.MyFrame    INFO    MyFrame.__init__() called.
main.MyApp      INFO    MyApp.OnInit() called.
main    INFO    Exiting program.

- 没有打开单独的窗口,尽管这些行

main.MyFrame    INFO    MyFrame.__init__() called.
main.MyApp      INFO    MyApp.OnInit() called.

应该在一个窗口中显示,而不是在控制台上。

我觉得 wx.App 不能把标准错误输出重定向到一个窗口,只要有一个日志实例使用标准错误输出。尽管 wxPython 的文档声称可以实现我想要的效果,请看这里。

有什么想法吗?

Uwe

2 个回答

1

我觉得更优雅的做法是创建一个自定义的日志处理器子类,这个子类可以把日志信息发送到一个特定的日志框架。

这样做可以让你在运行程序时更方便地开启或关闭图形界面的日志记录。

1

当 wx.App 说它会把输出和错误信息重定向到一个弹出窗口时,实际上是指它会把 sys.stdout 和 sys.stderr 的内容重定向到这个窗口。所以如果你直接写入 sys.stdout 或 sys.stderr,内容就会显示在弹出窗口里。例如,可以试试这个:

print "this will go to wx msg frame"
sys.stdout.write("yes it goes")
sys.stderr.write("... and this one too")

问题在于,如果在创建流处理器(streamhandler)之后再创建 wx.App,流处理器就会指向旧的(原始的)sys.stderr 和 sys.stdout,而不是 wx.App 设置的新版本。因此,更简单的解决办法是先创建 wx.App,再创建流处理器。例如,可以把 app = MyApp(redirect = True) 这行代码放在日志初始化代码之前。

另外,你也可以创建一个自定义的日志处理器,把数据写入 sys.stdout 和 sys.stderr,或者更好的是,自己创建一个窗口,把数据放到那个窗口里。例如,可以试试这个:

class LogginRedirectHandler(logging.Handler):
        def __init__(self,):
            # run the regular Handler __init__
            logging.Handler.__init__(self)

        def emit(self, record):
            sys.stdout.write(record.message)

loggingRedirectHandler = LogginRedirectHandler()
logger_file_handler.setLevel(logging.DEBUG)
logger.addHandler(loggingRedirectHandler)

撰写回答