wxPython: How to Redirect Python’s Logging Module to a TextCtrl

Today I was reading the wxPython Google group / mailing list and there was someone asking about how to make Python’s logging module write its output to file and to a TextCtrl. It turns out that you need to create a custom logging handler to do it. At first, I tried just using a normal StreamHandler and redirecting stdout via the sys module (sys.stdout) to my text control, but that would only redirect my print statements, not the log messages.

Let’s take a look at what I ended up with:

import logging
import logging.config
import wx

########################################################################
class CustomConsoleHandler(logging.StreamHandler):
    """"""
    
    #----------------------------------------------------------------------
    def __init__(self, textctrl):
        """"""
        logging.StreamHandler.__init__(self)
        self.textctrl = textctrl
        

    #----------------------------------------------------------------------
    def emit(self, record):
        """Constructor"""
        msg = self.format(record)
        self.textctrl.WriteText(msg + "\n")
        self.flush()

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.logger = logging.getLogger("wxApp")
        
        self.logger.info("Test from MyPanel __init__")
        
        logText = wx.TextCtrl(self,
                              style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        
        btn = wx.Button(self, label="Press Me")
        btn.Bind(wx.EVT_BUTTON, self.onPress)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(logText, 1, wx.EXPAND|wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL, 5)
        self.SetSizer(sizer)
                
        txtHandler = CustomConsoleHandler(logText)
        self.logger.addHandler(txtHandler)
        
    #----------------------------------------------------------------------
    def onPress(self, event):
        """
        """
        self.logger.error("Error Will Robinson!")
        self.logger.info("Informational message")
            
########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Logging test")
        panel = MyPanel(self)
        self.logger = logging.getLogger("wxApp")
        self.Show()
        
#----------------------------------------------------------------------
def main():
    """
    """
    dictLogConfig = {
        "version":1,
        "handlers":{
                    "fileHandler":{
                        "class":"logging.FileHandler",
                        "formatter":"myFormatter",
                        "filename":"test.log"
                        },
                    "consoleHandler":{
                        "class":"logging.StreamHandler",
                        "formatter":"myFormatter"
                        }
                    },        
        "loggers":{
            "wxApp":{
                "handlers":["fileHandler", "consoleHandler"],
                "level":"INFO",
                }
            },
 
        "formatters":{
            "myFormatter":{
                "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
                }
            }
        }
    logging.config.dictConfig(dictLogConfig)
    logger = logging.getLogger("wxApp")
    
    logger.info("This message came from main!")
    
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()
    
if __name__ == "__main__":
    main()

You will note that I ended up using Python’s logging.config module. The dictConfig method was added in Python 2.7, so if you don’t have that or better, then this code won’t work for you. Basically you set up your logging handler and formatters and what-not inside of dictionary and then pass it to logging.config. If you run this code, you will notice that the first couple of messages go to stdout and the log, but not to the text control. At the end of the panel class’s __init__, we add our custom handler and that’s when redirecting logging messages to the text control begins. You can press the button to see it in action!

You may also want to take a look at some of the references below. They help explain what I’m doing in more detail.

Related Articles

3 thoughts on “wxPython: How to Redirect Python’s Logging Module to a TextCtrl”

  1. That’s really useful thanks ! Never thought of it.

    btw why do you do ‘stream = self.stream’ in the emit method ? You are not using ‘stream’ after that…

Comments are closed.