How can I redirect the logger to a wxPython textCtrl using a custom logging handler?

后端 未结 3 682
灰色年华
灰色年华 2020-12-28 20:53

I\'m using a module in my python app that writes a lot a of messages using the logging module. Initially I was using this in a console application and it was pretty easy to

相关标签:
3条回答
  • 2020-12-28 21:39

    Create Handler

    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)
    

    Then in your control

    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()
    
    0 讨论(0)
  • 2020-12-28 21:49

    You will need to create a custom logging.Handler and add it to your logging.Logger.

    From the documentation:

    Handler objects are responsible for dispatching the appropriate log messages (based on the log messages’ severity) to the handler’s specified destination. Logger objects can add zero or more handler objects to themselves with an addHandler() method. As an example scenario, an application may want to send all log messages to a log file, all log messages of error or higher to stdout, and all messages of critical to an email address. This scenario requires three individual handlers where each handler is responsible for sending messages of a specific severity to a specific location.

    See http://docs.python.org/library/logging.html#handler-objects for the Handler API.

    In particular, it is the Handler.emit(record) method that you can implement to specify the destination of the output. Presumably, you would implement this to call TextCtrl.AppendText.

    0 讨论(0)
  • 2020-12-28 21:50

    Here's a simple working example:

    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()
    

    Screenshot:

    Screenshot of running script

    Update: As iondiode points out, this simple script may have problems if there are multiple threads in your app, all logging via such a handler; ideally only a UI thread should update the UI. You can use the suggested approach of logging the event by using a custom event, as per his answer.

    0 讨论(0)
提交回复
热议问题