如何使用自定义日志处理器将日志重定向到wxPython的textCtrl?

12 投票
4 回答
11779 浏览
提问于 2025-04-15 22:37

我在我的Python应用程序中使用了一个模块,它会生成很多日志信息,主要是通过logging模块来实现的。最开始我是在一个控制台应用中使用这个模块,输出日志信息到控制台非常简单,只需要用一个控制台处理器就可以了。现在我用wxPython开发了一个图形用户界面(GUI)版本的应用,我想把所有的日志信息显示到一个自定义的控件上——一个多行文本框。请问有没有办法创建一个自定义的日志处理器,这样我就可以把所有的日志信息重定向到这个文本框里,并且可以随意显示这些日志信息——在这种情况下,就是在wxPython应用中。

4 个回答

3

你需要创建一个自定义的 logging.Handler,然后把它添加到你的 logging.Logger 中。

根据文档的说明:

Handler 对象负责把合适的日志信息(根据日志信息的严重程度)发送到指定的地方。Logger 对象可以通过 addHandler() 方法添加一个或多个 handler 对象。举个例子,一个应用程序可能想把所有的日志信息发送到一个日志文件,把所有错误及以上级别的日志信息发送到标准输出(stdout),而把所有严重级别的日志信息发送到一个邮箱。这种情况下就需要三个不同的 handler,每个 handler 负责把特定严重程度的消息发送到特定的位置。

想了解更多关于 Handler 的信息,可以查看 http://docs.python.org/library/logging.html#handler-objects

特别需要注意的是,你可以实现 Handler.emit(record) 方法来指定输出的目的地。通常情况下,你会实现这个方法来调用 TextCtrl.AppendText

5

这是一个简单的工作示例:

import logging
import random
import sys
import wx

logger = logging.getLogger(__name__)

class WxTextCtrlHandler(logging.Handler):
    def __init__(self, ctrl):
        logging.Handler.__init__(self)
        self.ctrl = ctrl

    def emit(self, record):
        s = self.format(record) + '\n'
        wx.CallAfter(self.ctrl.WriteText, s)

LEVELS = [
    logging.DEBUG,
    logging.INFO,
    logging.WARNING,
    logging.ERROR,
    logging.CRITICAL
]

class Frame(wx.Frame):

    def __init__(self):
        TITLE = "wxPython Logging To A Control"
        wx.Frame.__init__(self, None, wx.ID_ANY, TITLE)

        panel = wx.Panel(self, wx.ID_ANY)
        log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
                          style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        btn = wx.Button(panel, wx.ID_ANY, 'Log something!')
        self.Bind(wx.EVT_BUTTON, self.onButton, btn)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)
        handler = WxTextCtrlHandler(log)
        logger.addHandler(handler)
        FORMAT = "%(asctime)s %(levelname)s %(message)s"
        handler.setFormatter(logging.Formatter(FORMAT))
        logger.setLevel(logging.DEBUG)

    def onButton(self, event):
        logger.log(random.choice(LEVELS), "More? click again!")

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = Frame().Show()
    app.MainLoop()

截图:

运行脚本的截图

更新:正如iondiode所指出的,如果你的应用程序中有多个线程都通过这样的处理程序进行日志记录,这个简单的脚本可能会出现问题;理想情况下,只有UI线程应该更新用户界面。你可以按照他的回答,使用自定义事件来记录事件,这样做会更好。

14

创建处理程序

import wx
import wx.lib.newevent

import logging

# create event type
wxLogEvent, EVT_WX_LOG_EVENT = wx.lib.newevent.NewEvent()


class wxLogHandler(logging.Handler):
    """
    A handler class which sends log strings to a wx object
    """
    def __init__(self, wxDest=None):
        """
        Initialize the handler
        @param wxDest: the destination object to post the event to 
        @type wxDest: wx.Window
        """
        logging.Handler.__init__(self)
        self.wxDest = wxDest
        self.level = logging.DEBUG

    def flush(self):
        """
        does nothing for this handler
        """


    def emit(self, record):
        """
        Emit a record.

        """
        try:
            msg = self.format(record)
            evt = wxLogEvent(message=msg,levelname=record.levelname)            
            wx.PostEvent(self.wxDest,evt)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

然后在你的控制器中

self.Bind(EVT_WX_LOG_EVENT, self.onLogEvent)

def onLogEvent(self,event):
    '''
    Add event.message to text window
    '''
    msg = event.message.strip("\r")+"\n"
    self.logwindow.AppendText(msg) # or whatevery
    event.Skip()

撰写回答