Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 get the logging output to display on the console using a console handler. Now I've developed a GUI version of my app using wxPython and I'd like to display all the logging output to a custom control — a multi-line textCtrl. Is there a way i could create a custom logging handler so i can redirect all the logging output there and display the logging messages wherever/however I want — in this case, a wxPython app.

like image 247
Mridang Agarwalla Avatar asked May 12 '10 14:05

Mridang Agarwalla


2 Answers

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.

like image 120
Vinay Sajip Avatar answered Sep 28 '22 06:09

Vinay Sajip


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()
like image 31
iondiode Avatar answered Sep 28 '22 06:09

iondiode